//Written in the D programming language

/++
    Module containing Date/Time functionality.

    This module provides:
    $(UL
        $(LI Types to represent points in time: $(LREF SysTime), $(LREF Date),
             $(LREF TimeOfDay), and $(LREF2 .DateTime, DateTime).)
        $(LI Types to represent intervals of time.)
        $(LI Types to represent ranges over intervals of time.)
        $(LI Types to represent time zones (used by $(LREF SysTime)).)
        $(LI A platform-independent, high precision stopwatch type:
             $(LREF StopWatch))
        $(LI Benchmarking functions.)
        $(LI Various helper functions.)
    )

    Closely related to std.datetime is <a href="core_time.html">$(D core.time)</a>,
    and some of the time types used in std.datetime come from there - such as
    $(CXREF time, Duration), $(CXREF time, TickDuration), and
    $(CXREF time, FracSec).
    core.time is publically imported into std.datetime, it isn't necessary
    to import it separately.

    Three of the main concepts used in this module are time points, time
    durations, and time intervals.

    A time point is a specific point in time. e.g. January 5th, 2010
    or 5:00.

    A time duration is a length of time with units. e.g. 5 days or 231 seconds.

    A time interval indicates a period of time associated with a fixed point in
    time. It is either two time points associated with each other,
    indicating the time starting at the first point up to, but not including,
    the second point - e.g. [January 5th, 2010 - March 10th, 2010$(RPAREN) - or
    it is a time point and a time duration associated with one another. e.g.
    January 5th, 2010 and 5 days, indicating [January 5th, 2010 -
    January 10th, 2010$(RPAREN).

    Various arithmetic operations are supported between time points and
    durations (e.g. the difference between two time points is a time duration),
    and ranges can be gotten from time intervals, so range-based operations may
    be done on a series of time points.

    The types that the typical user is most likely to be interested in are
    $(LREF Date) (if they want dates but don't care about time), $(LREF DateTime)
    (if they want dates and times but don't care about time zones), $(LREF SysTime)
    (if they want the date and time from the OS and/or do care about time
    zones), and StopWatch (a platform-independent, high precision stop watch).
    $(LREF Date) and $(LREF DateTime) are optimized for calendar-based operations,
    while $(LREF SysTime) is designed for dealing with time from the OS. Check out
    their specific documentation for more details.

    To get the current time, use $(LREF2 .Clock.currTime, Clock.currTime).
    It will return the current
    time as a $(LREF SysTime). To print it, $(D toString) is
    sufficient, but if using $(D toISOString), $(D toISOExtString), or
    $(D toSimpleString), use the corresponding $(D fromISOString),
    $(D fromISOExtString), or $(D fromSimpleString) to create a
    $(LREF SysTime) from the string.

--------------------
auto currentTime = Clock.currTime();
auto timeString = currentTime.toISOExtString();
auto restoredTime = SysTime.fromISOExtString(timeString);
--------------------

    Various functions take a string (or strings) to represent a unit of time
    (e.g. $(D convert!("days", "hours")(numDays))). The valid strings to use
    with such functions are $(D "years"), $(D "months"), $(D "weeks"),
    $(D "days"), $(D "hours"), $(D "minutes"), $(D "seconds"),
    $(D "msecs") (milliseconds), $(D "usecs") (microseconds),
    $(D "hnsecs") (hecto-nanoseconds - i.e. 100 ns), or some subset thereof.
    There are a few functions in core.time which take $(D "nsecs"), but because
    nothing in std.datetime has precision greater than hnsecs, and very little
    in core.time does, no functions in std.datetime accept $(D "nsecs").
    To remember which units are abbreviated and which aren't,
    all units seconds and greater use their full names, and all
    sub-second units are abbreviated (since they'd be rather long if they
    weren't).

    Note:
        $(LREF DateTimeException) is an alias for $(CXREF time, TimeException),
        so you don't need to worry about core.time functions and std.datetime
        functions throwing different exception types (except in the rare case
        that they throw something other than $(CXREF time, TimeException) or
        $(LREF DateTimeException)).

    See_Also:
        $(DDLINK intro-to-_datetime, Introduction to std.datetime,
                 Introduction to std&#46;_datetime)<br>
        $(WEB en.wikipedia.org/wiki/ISO_8601, ISO 8601)<br>
        $(WEB en.wikipedia.org/wiki/Tz_database,
              Wikipedia entry on TZ Database)<br>
        $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones,
              List of Time Zones)<br>

    Copyright: Copyright 2010 - 2015
    License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
    Authors:   Jonathan M Davis and Kato Shoichi
    Source:    $(PHOBOSSRC std/_datetime.d)
    Macros:
        LREF2=<a href="#$1">$(D $2)</a>
+/
module std.datetime;

public import core.time;

import core.exception;
import core.stdc.time;

import std.exception;
import std.range.primitives;
import std.traits;
// FIXME
import std.functional; //: unaryFun;

version(Windows)
{
    import core.sys.windows.windows;
    import core.sys.windows.winsock2;
    import std.windows.registry;

    // Uncomment and run unittests to print missing Windows TZ translations.
    // Please subscribe to Microsoft Daylight Saving Time & Time Zone Blog
    // (https://blogs.technet.microsoft.com/dst2007/) if you feel responsible
    // for updating the translations.
    // version = UpdateWindowsTZTranslations;
}
else version(Posix)
{
    import core.sys.posix.stdlib;
    import core.sys.posix.sys.time;
}

version(unittest)
{
    import std.stdio;
}

unittest
{
    initializeTests();
}

//Verify module example.
unittest
{
    auto currentTime = Clock.currTime();
    auto timeString = currentTime.toISOExtString();
    auto restoredTime = SysTime.fromISOExtString(timeString);
}

//Verify Examples for core.time.Duration which couldn't be in core.time.
unittest
{
    assert(std.datetime.Date(2010, 9, 7) + dur!"days"(5) ==
           std.datetime.Date(2010, 9, 12));

    assert(std.datetime.Date(2010, 9, 7) - std.datetime.Date(2010, 10, 3) ==
           dur!"days"(-26));
}


//==============================================================================
// Section with public enums and constants.
//==============================================================================

/++
    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      ///
                   }

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

/++
    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.
  +/
enum AllowDayOverflow
{
    /// No, don't allow day overflow.
    no,

    /// Yes, allow day overflow.
    yes
}

/++
    Indicates a direction in time. One example of its use is $(LREF2 .Interval, Interval)'s
    $(LREF expand, expand) function which uses it to indicate whether the interval should
    be expanded backwards (into the past), forwards (into the future), or both.
  +/
enum Direction
{
    /// Backward.
    bwd,

    /// Forward.
    fwd,

    /// Both backward and forward.
    both
}

/++
    Used to indicate whether $(D popFront) should be called immediately upon
    creating a range. The idea is that for some functions used to generate a
    range for an interval, $(D front) is not necessarily a time point which
    would ever be generated by the range. To get the first time point
    in the range to match what the function generates, then use
    $(D PopFirst.yes) to indicate that the range should have $(D popFront)
    called on it before the range is returned so that $(D front) is a time point
    which the function would generate.

    For instance, if the function used to generate a range of time points
    generated successive Easters (i.e. you're iterating over all of the Easters
    within the interval), the initial date probably isn't an Easter. Using
    $(D PopFirst.yes) would tell the function which returned the
    range that $(D popFront) was to be called so that front would then be
    an Easter - the next one generated by the function (which when
    iterating forward would be the Easter following the original $(D front),
    while when iterating backward, it would be the Easter prior to the
    original $(D front)). If $(D PopFirst.no) were used, then $(D front) would
    remain the original time point and it would not necessarily be a time point
    which would be generated by the range-generating function (which in many
    cases is exactly what is desired -
    e.g. if iterating over every day starting at the beginning
    of the interval).
  +/
enum PopFirst
{
    /// No, don't call popFront() before returning the range.
    no,

    /// Yes, call popFront() before returning the range.
    yes
}

/++
   Used by StopWatch to indicate whether it should start immediately upon
   construction.
  +/
enum AutoStart
{
    /// No, don't start the StopWatch when it is constructed.
    no,

    /// Yes, do start the StopWatch when it is constructed.
    yes
}

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

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


//==============================================================================
// Section with other types.
//==============================================================================

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

/++
    Effectively a namespace to make it clear that the methods it contains are
    getting the time from the system clock. It cannot be instantiated.
 +/
final class Clock
{
public:

    /++
        Returns the current time in the given time zone.

        Params:
            clockType = The $(CXREF time, ClockType) indicates which system
                        clock to use to get the current time. Very few programs
                        need to use anything other than the default.
            tz = The time zone for the SysTime that's returned.

        Throws:
            $(LREF DateTimeException) if it fails to get the time.
      +/
    static SysTime currTime(ClockType clockType = ClockType.normal)(immutable TimeZone tz = LocalTime()) @safe
    {
        return SysTime(currStdTime!clockType, tz);
    }

    unittest
    {
        import std.format : format;
        assert(currTime().timezone is LocalTime());
        assert(currTime(UTC()).timezone is UTC());

        // core.stdc.time.time does not always use unix time on Windows systems.
        // In particular, dmc does not use unix time. If we can guarantee that
        // the MS runtime uses unix time, then we may be able run this test
        // then, but for now, we're just not going to run this test on Windows.
        version(Posix)
        {
            static import std.math;
            immutable unixTimeD = currTime().toUnixTime();
            immutable unixTimeC = core.stdc.time.time(null);
            assert(std.math.abs(unixTimeC - unixTimeD) <= 2);
        }

        auto norm1 = Clock.currTime;
        auto norm2 = Clock.currTime(UTC());
        assert(norm1 <= norm2, format("%s %s", norm1, norm2));
        assert(abs(norm1 - norm2) <= seconds(2));

        import std.meta : AliasSeq;
        foreach(ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second))
        {
            scope(failure) writefln("ClockType.%s", ct);
            auto value1 = Clock.currTime!ct;
            auto value2 = Clock.currTime!ct(UTC());
            assert(value1 <= value2, format("%s %s", value1, value2));
            assert(abs(value1 - value2) <= seconds(2));
        }
    }


    /++
        Returns the number of hnsecs since midnight, January 1st, 1 A.D. for the
        current time.

        Params:
            clockType = The $(CXREF time, ClockType) indicates which system
                        clock to use to get the current time. Very few programs
                        need to use anything other than the default.

        Throws:
            $(LREF DateTimeException) if it fails to get the time.
      +/
    static @property long currStdTime(ClockType clockType = ClockType.normal)() @trusted
    {
        static if(clockType != ClockType.coarse &&
                  clockType != ClockType.normal &&
                  clockType != ClockType.precise &&
                  clockType != ClockType.second)
        {
            import std.format : format;
            static assert(0, format("ClockType.%s is not supported by Clock.currTime or Clock.currStdTime", clockType));
        }

        version(Windows)
        {
            FILETIME fileTime;
            GetSystemTimeAsFileTime(&fileTime);
            immutable result = FILETIMEToStdTime(&fileTime);
            static if(clockType == ClockType.second)
            {
                // Ideally, this would use core.std.time.time, but the C runtime
                // has to be using unix time for that to work, and that's not
                // guaranteed on Windows. Digital Mars does not use unix time.
                // MS may or may not. If it does, then this can be made to use
                // core.stdc.time for MS, but for now, we'll leave it like this.
                return convert!("seconds", "hnsecs")(convert!("hnsecs", "seconds")(result));
            }
            else
                return result;
        }
        else version(Posix)
        {
            enum hnsecsToUnixEpoch = unixTimeToStdTime(0);

            version(OSX)
            {
                static if(clockType == ClockType.second)
                    return unixTimeToStdTime(core.stdc.time.time(null));
                else
                {
                    timeval tv;
                    if(gettimeofday(&tv, null) != 0)
                        throw new TimeException("Call to gettimeofday() failed");
                    return convert!("seconds", "hnsecs")(tv.tv_sec) +
                           convert!("usecs", "hnsecs")(tv.tv_usec) +
                           hnsecsToUnixEpoch;
                }
            }
            else version(linux)
            {
                static if(clockType == ClockType.second)
                    return unixTimeToStdTime(core.stdc.time.time(null));
                else
                {
                    import core.sys.linux.time;
                    static if(clockType == ClockType.coarse)       alias clockArg = CLOCK_REALTIME_COARSE;
                    else static if(clockType == ClockType.normal)  alias clockArg = CLOCK_REALTIME;
                    else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME;
                    else static assert(0, "Previous static if is wrong.");
                    timespec ts;
                    if(clock_gettime(clockArg, &ts) != 0)
                        throw new TimeException("Call to clock_gettime() failed");
                    return convert!("seconds", "hnsecs")(ts.tv_sec) +
                           ts.tv_nsec / 100 +
                           hnsecsToUnixEpoch;
                }
            }
            else version(FreeBSD)
            {
                import core.sys.freebsd.time;
                static if(clockType == ClockType.coarse)       alias clockArg = CLOCK_REALTIME_FAST;
                else static if(clockType == ClockType.normal)  alias clockArg = CLOCK_REALTIME;
                else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME_PRECISE;
                else static if(clockType == ClockType.second)  alias clockArg = CLOCK_SECOND;
                else static assert(0, "Previous static if is wrong.");
                timespec ts;
                if(clock_gettime(clockArg, &ts) != 0)
                    throw new TimeException("Call to clock_gettime() failed");
                return convert!("seconds", "hnsecs")(ts.tv_sec) +
                       ts.tv_nsec / 100 +
                       hnsecsToUnixEpoch;
            }
            else version(NetBSD)
            {
                static if(clockType == ClockType.second)
                    return unixTimeToStdTime(core.stdc.time.time(null));
                else
                {
                    timeval tv;
                    if(gettimeofday(&tv, null) != 0)
                        throw new TimeException("Call to gettimeofday() failed");
                    return convert!("seconds", "hnsecs")(tv.tv_sec) +
                           convert!("usecs", "hnsecs")(tv.tv_usec) +
                           hnsecsToUnixEpoch;
                }
            }
            else version(Solaris)
            {
                static if(clockType == ClockType.second)
                    return unixTimeToStdTime(core.stdc.time.time(null));
                else
                {
                    import core.sys.solaris.time;
                    static if(clockType == ClockType.coarse)       alias clockArg = CLOCK_REALTIME;
                    else static if(clockType == ClockType.normal)  alias clockArg = CLOCK_REALTIME;
                    else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME;
                    else static assert(0, "Previous static if is wrong.");
                    timespec ts;
                    if(clock_gettime(clockArg, &ts) != 0)
                        throw new TimeException("Call to clock_gettime() failed");
                    return convert!("seconds", "hnsecs")(ts.tv_sec) +
                           ts.tv_nsec / 100 +
                           hnsecsToUnixEpoch;
                }
            }
            else static assert(0, "Unsupported OS");
        }
        else static assert(0, "Unsupported OS");
    }

    unittest
    {
        import std.math : abs;
        import std.format : format;
        enum limit = convert!("seconds", "hnsecs")(2);

        auto norm1 = Clock.currStdTime;
        auto norm2 = Clock.currStdTime;
        assert(norm1 <= norm2, format("%s %s", norm1, norm2));
        assert(abs(norm1 - norm2) <= limit);

        import std.meta : AliasSeq;
        foreach(ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second))
        {
            scope(failure) writefln("ClockType.%s", ct);
            auto value1 = Clock.currStdTime!ct;
            auto value2 = Clock.currStdTime!ct;
            assert(value1 <= value2, format("%s %s", value1, value2));
            assert(abs(value1 - value2) <= limit);
        }
    }


    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime). Use $(D MonoTime.currTime)
              instead. currSystemTick will be removed in January 2017.)

        The current system tick. The number of ticks per second varies from
        system to system. currSystemTick uses a monotonic clock, so it's
        intended for precision timing by comparing relative time values, not
        for getting the current system time.

        Warning:
            On some systems, the monotonic clock may stop counting when
            the computer goes to sleep or hibernates. So, the monotonic
            clock could be off if that occurs. This is known to happen
            on Mac OS X. It has not been tested whether it occurs on
            either Windows or Linux.

        Throws:
            $(LREF DateTimeException) if it fails to get the time.
      +/
    deprecated("Use core.time.MonoTime.currTime instead")
    static @property TickDuration currSystemTick() @safe nothrow
    {
        return TickDuration.currSystemTick;
    }

    deprecated unittest
    {
        assert(Clock.currSystemTick.length > 0);
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime). To duplicate the behavior
              of currAppTick with $(D MonoTime), store the value of
              $(D MonoTime.currTime) when the program starts, and then subtract
              it from the current value of $(D MonoTime.currTime) in order to
              determine how long the program has been running. currAppTick will
              be removed in January 2017.)

        --------------------
        immutable MonoTime startupTime;
        shared static this()
        {
            startupTime = MonoTime.currTime;
        }
        Duration timeSinceProgramStarted()
        {
            return MonoTime.currTime - startupTime;
        }
        --------------------

        The current number of system ticks since the application started.
        The number of ticks per second varies from system to system.
        This uses a monotonic clock.

        Warning:
            On some systems, the monotonic clock may stop counting when
            the computer goes to sleep or hibernates. So, the monotonic
            clock could be off if that occurs. This is known to happen
            on Mac OS X. It has not been tested whether it occurs on
            either Windows or on Linux.

        Throws:
            $(LREF DateTimeException) if it fails to get the time.
      +/
    deprecated("Use core.time.MonoTime instead. See currAppTick's documentation for details.")
    static @property TickDuration currAppTick() @safe
    {
        return currSystemTick - TickDuration.appOrigin;
    }

    deprecated unittest
    {
        auto a = Clock.currSystemTick;
        auto b = Clock.currAppTick;
        assert(a.length);
        assert(b.length);
        assert(a > b);
    }

private:

    @disable this() {}
}

//==============================================================================
// Section with time points.
//==============================================================================

/++
    $(D SysTime) is the type used to get the current time from the
    system or doing anything that involves time zones. Unlike
    $(LREF DateTime), the time zone is an integral part of $(D SysTime) (though for
    local time applications, time zones can be ignored and
    it will work, since it defaults to using the local time zone). It holds its
    internal time in std time (hnsecs since midnight, January 1st, 1 A.D. UTC),
    so it interfaces well with the system time. However, that means that, unlike
    $(LREF DateTime), it is not optimized for calendar-based operations, and
    getting individual units from it such as years or days is going to involve
    conversions and be less efficient.

    For calendar-based operations that don't
    care about time zones, then $(LREF DateTime) would be the type to
    use. For system time, use $(D SysTime).

    $(LREF2 .Clock.currTime, Clock.currTime) will return the current time as a $(D SysTime).
    To convert a $(D SysTime) to a $(LREF Date) or $(LREF DateTime), simply cast
    it. To convert a $(LREF Date) or $(LREF DateTime) to a
    $(D SysTime), use $(D SysTime)'s constructor, and pass in the
    intended time zone with it (or don't pass in a $(LREF2 .TimeZone, TimeZone), and the local
    time zone will be used). Be aware, however, that converting from a
    $(LREF DateTime) to a $(D SysTime) will not necessarily be 100% accurate due to
    DST (one hour of the year doesn't exist and another occurs twice).
    To not risk any conversion errors, keep times as
    $(D SysTime)s. Aside from DST though, there shouldn't be any conversion
    problems.

    For using time zones other than local time or UTC, use
    $(LREF PosixTimeZone) on Posix systems (or on Windows, if providing the TZ
    Database files), and use $(LREF WindowsTimeZone) on Windows systems.
    The time in $(D SysTime) is kept internally in hnsecs from midnight,
    January 1st, 1 A.D. UTC. Conversion error cannot happen when changing
    the time zone of a $(D SysTime). $(LREF LocalTime) is the $(LREF2 .TimeZone, TimeZone) class
    which represents the local time, and $(D UTC) is the $(LREF2 .TimeZone, TimeZone) class
    which represents UTC. $(D SysTime) uses $(LREF LocalTime) if no $(LREF2 .TimeZone, TimeZone)
    is provided. For more details on time zones, see the documentation for
    $(LREF2 .TimeZone, TimeZone), $(LREF PosixTimeZone), and $(LREF WindowsTimeZone).

    $(D SysTime)'s range is from approximately 29,000 B.C. to approximately
    29,000 A.D.
  +/
struct SysTime
{
    import std.typecons : Rebindable;

public:

    /++
        Params:
            dateTime = The $(LREF DateTime) to use to set this $(LREF SysTime)'s
                       internal std time. As $(LREF DateTime) has no concept of
                       time zone, tz is used as its time zone.
            tz       = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
                       $(LREF LocalTime) will be used. The given $(LREF DateTime) is
                       assumed to be in the given time zone.
      +/
    this(in DateTime dateTime, immutable TimeZone tz = null) @safe nothrow
    {
        try
            this(dateTime, Duration.zero, tz);
        catch(Exception e)
            assert(0, "SysTime's constructor threw when it shouldn't have.");
    }

    unittest
    {
        import std.format : format;
        static void test(DateTime dt, immutable TimeZone tz, long expected)
        {
            auto sysTime = SysTime(dt, tz);
            assert(sysTime._stdTime == expected);
            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
                   format("Given DateTime: %s", dt));
        }

        test(DateTime.init, UTC(), 0);
        test(DateTime(1, 1, 1, 12, 30, 33), UTC(), 450_330_000_000L);
        test(DateTime(0, 12, 31, 12, 30, 33), UTC(), -413_670_000_000L);
        test(DateTime(1, 1, 1, 0, 0, 0), UTC(), 0);
        test(DateTime(1, 1, 1, 0, 0, 1), UTC(), 10_000_000L);
        test(DateTime(0, 12, 31, 23, 59, 59), UTC(), -10_000_000L);

        test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(dur!"minutes"(-60)), 36_000_000_000L);
        test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero), 0);
        test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(dur!"minutes"(60)), -36_000_000_000L);
    }

    /++
        Params:
            dateTime = The $(LREF DateTime) to use to set this $(LREF SysTime)'s
                       internal std time. As $(LREF DateTime) has no concept of
                       time zone, tz is used as its time zone.
            fracSecs = The fractional seconds portion of the time.
            tz       = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
                       $(LREF LocalTime) will be used. The given $(LREF DateTime) is
                       assumed to be in the given time zone.

        Throws:
            $(LREF DateTimeException) if $(D fracSecs) is negative or if it's
            greater than or equal to one second.
      +/
    this(in DateTime dateTime, in Duration fracSecs, immutable TimeZone tz = null) @safe
    {
        enforce(fracSecs >= Duration.zero, new DateTimeException("A SysTime cannot have negative fractional seconds."));
        enforce(fracSecs < seconds(1), new DateTimeException("Fractional seconds must be less than one second."));
        auto nonNullTZ = tz is null ? LocalTime() : tz;

        immutable dateDiff = dateTime.date - Date.init;
        immutable todDiff = dateTime.timeOfDay - TimeOfDay.init;

        immutable adjustedTime = dateDiff + todDiff + fracSecs;
        immutable standardTime = nonNullTZ.tzToUTC(adjustedTime.total!"hnsecs");

        this(standardTime, nonNullTZ);
    }

    unittest
    {
        import std.format : format;
        static void test(DateTime dt, Duration fracSecs, immutable TimeZone tz, long expected)
        {
            auto sysTime = SysTime(dt, fracSecs, tz);
            assert(sysTime._stdTime == expected);
            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
                   format("Given DateTime: %s, Given Duration: %s", dt, fracSecs));
        }

        test(DateTime.init, Duration.zero, UTC(), 0);
        test(DateTime(1, 1, 1, 12, 30, 33), Duration.zero, UTC(), 450_330_000_000L);
        test(DateTime(0, 12, 31, 12, 30, 33), Duration.zero, UTC(), -413_670_000_000L);
        test(DateTime(1, 1, 1, 0, 0, 0), msecs(1), UTC(), 10_000L);
        test(DateTime(0, 12, 31, 23, 59, 59), msecs(999), UTC(), -10_000L);

        test(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC(), -1);
        test(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC(), -9_999_999);
        test(DateTime(0, 12, 31, 23, 59, 59), Duration.zero, UTC(), -10_000_000);

        assertThrown!DateTimeException(SysTime(DateTime.init, hnsecs(-1), UTC()));
        assertThrown!DateTimeException(SysTime(DateTime.init, seconds(1), UTC()));
    }

    // @@@DEPRECATED_2016-08@@@
    /++
        $(RED Deprecated. Please use the overload which takes a
              $(CXREF time, Duration) for the fractional seconds. This overload
              will be removed in August 2016.)

        Params:
            dateTime = The $(LREF DateTime) to use to set this $(LREF SysTime)'s
                       internal std time. As $(LREF DateTime) has no concept of
                       time zone, tz is used as its time zone.
            fracSec  = The fractional seconds portion of the time.
            tz       = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
                       $(LREF LocalTime) will be used. The given $(LREF DateTime) is
                       assumed to be in the given time zone.

        Throws:
            $(LREF DateTimeException) if $(D fracSec) is negative.
      +/
    deprecated("Please use the overload which takes a Duration instead of a FracSec.")
    this(in DateTime dateTime, in FracSec fracSec, immutable TimeZone tz = null) @safe
    {
        immutable fracHNSecs = fracSec.hnsecs;
        enforce(fracHNSecs >= 0, new DateTimeException("A SysTime cannot have negative fractional seconds."));
        _timezone = tz is null ? LocalTime() : tz;

        try
        {
            immutable dateDiff = (dateTime.date - Date(1, 1, 1)).total!"hnsecs";
            immutable todDiff = (dateTime.timeOfDay - TimeOfDay(0, 0, 0)).total!"hnsecs";

            immutable adjustedTime = dateDiff + todDiff + fracHNSecs;
            immutable standardTime = _timezone.tzToUTC(adjustedTime);

            this(standardTime, _timezone);
        }
        catch(Exception e)
            assert(0, "Date, TimeOfDay, or DateTime's constructor threw when it shouldn't have.");
    }

    deprecated unittest
    {
        import std.format : format;

        static void test(DateTime dt,
                         FracSec fracSec,
                         immutable TimeZone tz,
                         long expected)
        {
            auto sysTime = SysTime(dt, fracSec, tz);
            assert(sysTime._stdTime == expected);
            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
                   format("Given DateTime: %s, Given FracSec: %s", dt, fracSec));
        }

        test(DateTime.init, FracSec.init, UTC(), 0);
        test(DateTime(1, 1, 1, 12, 30, 33), FracSec.init, UTC(), 450_330_000_000L);
        test(DateTime(0, 12, 31, 12, 30, 33), FracSec.init, UTC(), -413_670_000_000L);
        test(DateTime(1, 1, 1, 0, 0, 0), FracSec.from!"msecs"(1), UTC(), 10_000L);
        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"msecs"(999), UTC(), -10_000L);

        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(9_999_999), UTC(), -1);
        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(1), UTC(), -9_999_999);
        test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(0), UTC(), -10_000_000);

        assertThrown!DateTimeException(SysTime(DateTime.init, FracSec.from!"hnsecs"(-1), UTC()));
    }

    /++
        Params:
            date = The $(LREF Date) to use to set this $(LREF SysTime)'s internal std
                   time. As $(LREF Date) has no concept of time zone, tz is used as
                   its time zone.
            tz   = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
                   $(LREF LocalTime) will be used. The given $(LREF Date) is assumed
                   to be in the given time zone.
      +/
    this(in Date date, immutable TimeZone tz = null) @safe nothrow
    {
        _timezone = tz is null ? LocalTime() : tz;

        try
        {
            immutable adjustedTime = (date - Date(1, 1, 1)).total!"hnsecs";
            immutable standardTime = _timezone.tzToUTC(adjustedTime);

            this(standardTime, _timezone);
        }
        catch(Exception e)
            assert(0, "Date's constructor through when it shouldn't have.");
    }

    unittest
    {
        static void test(Date d, immutable TimeZone tz, long expected)
        {
            import std.format : format;
            auto sysTime = SysTime(d, tz);
            assert(sysTime._stdTime == expected);
            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
                   format("Given Date: %s", d));
        }

        test(Date.init, UTC(), 0);
        test(Date(1, 1, 1), UTC(), 0);
        test(Date(1, 1, 2), UTC(), 864000000000);
        test(Date(0, 12, 31), UTC(), -864000000000);
    }

    /++
        Note:
            Whereas the other constructors take in the given date/time, assume
            that it's in the given time zone, and convert it to hnsecs in UTC
            since midnight, January 1st, 1 A.D. UTC - i.e. std time - this
            constructor takes a std time, which is specifically already in UTC,
            so no conversion takes place. Of course, the various getter
            properties and functions will use the given time zone's conversion
            function to convert the results to that time zone, but no conversion
            of the arguments to this constructor takes place.

        Params:
            stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. UTC.
            tz      = The $(LREF2 .TimeZone, TimeZone) to use for this $(LREF SysTime). If null,
                      $(LREF LocalTime) will be used.
      +/
    this(long stdTime, immutable TimeZone tz = null) @safe pure nothrow
    {
        _stdTime = stdTime;
        _timezone = tz is null ? LocalTime() : tz;
    }

    unittest
    {
        static void test(long stdTime, immutable TimeZone tz)
        {
            import std.format : format;
            auto sysTime = SysTime(stdTime, tz);
            assert(sysTime._stdTime == stdTime);
            assert(sysTime._timezone is (tz is null ? LocalTime() : tz),
                   format("Given stdTime: %s", stdTime));
        }

        foreach(stdTime; [-1234567890L, -250, 0, 250, 1235657390L])
        {
            foreach(tz; testTZs)
                test(stdTime, tz);
        }
    }

    /++
        Params:
            rhs = The $(LREF SysTime) to assign to this one.
      +/
    ref SysTime opAssign(const ref SysTime rhs) return @safe pure nothrow
    {
        _stdTime = rhs._stdTime;
        _timezone = rhs._timezone;

        return this;
    }

    /++
        Params:
            rhs = The $(LREF SysTime) to assign to this one.
      +/
    ref SysTime opAssign(SysTime rhs) return @safe pure nothrow
    {
        _stdTime = rhs._stdTime;
        _timezone = rhs._timezone;

        return this;
    }

    /++
        Checks for equality between this $(LREF SysTime) and the given
        $(LREF SysTime).

        Note that the time zone is ignored. Only the internal
        std times (which are in UTC) are compared.
     +/
    bool opEquals(const SysTime rhs) @safe const pure nothrow
    {
        return opEquals(rhs);
    }

    /// ditto
    bool opEquals(const ref SysTime rhs) @safe const pure nothrow
    {
        return _stdTime == rhs._stdTime;
    }

    unittest
    {
        import std.range;
        assert(SysTime(DateTime.init, UTC()) == SysTime(0, UTC()));
        assert(SysTime(DateTime.init, UTC()) == SysTime(0));
        assert(SysTime(Date.init, UTC()) == SysTime(0));
        assert(SysTime(0) == SysTime(0));

        static void test(DateTime dt,
                         immutable TimeZone tz1,
                         immutable TimeZone tz2)
        {
            auto st1 = SysTime(dt);
            st1.timezone = tz1;

            auto st2 = SysTime(dt);
            st2.timezone = tz2;

            assert(st1 == st2);
        }

        foreach(tz1; testTZs)
        {
            foreach(tz2; testTZs)
            {
                foreach(dt; chain(testDateTimesBC, testDateTimesAD))
                        test(dt, tz1, tz2);
            }
        }

        auto st = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
        assert(st == st);
        assert(st == cst);
        //assert(st == ist);
        assert(cst == st);
        assert(cst == cst);
        //assert(cst == ist);
        //assert(ist == st);
        //assert(ist == cst);
        //assert(ist == ist);
    }

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

        Time zone is irrelevant when comparing $(LREF SysTime)s.

        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(in SysTime rhs) @safe const pure nothrow
    {
        if(_stdTime < rhs._stdTime)
            return -1;
        if(_stdTime > rhs._stdTime)
            return 1;

        return 0;
    }

    unittest
    {
        import std.range;
        assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0, UTC())) == 0);
        assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0)) == 0);
        assert(SysTime(Date.init, UTC()).opCmp(SysTime(0)) == 0);
        assert(SysTime(0).opCmp(SysTime(0)) == 0);

        static void testEqual(SysTime st,
                              immutable TimeZone tz1,
                              immutable TimeZone tz2)
        {
            auto st1 = st;
            st1.timezone = tz1;

            auto st2 = st;
            st2.timezone = tz2;

            assert(st1.opCmp(st2) == 0);
        }

        auto sts = array(map!SysTime(chain(testDateTimesBC, testDateTimesAD)));

        foreach(st; sts)
            foreach(tz1; testTZs)
                foreach(tz2; testTZs)
                    testEqual(st, tz1, tz2);

        static void testCmp(SysTime st1,
                            immutable TimeZone tz1,
                            SysTime st2,
                            immutable TimeZone tz2)
        {
            st1.timezone = tz1;
            st2.timezone = tz2;
            assert(st1.opCmp(st2) < 0);
            assert(st2.opCmp(st1) > 0);
        }

        foreach(si, st1; sts)
            foreach(st2; sts[si+1 .. $])
                foreach(tz1; testTZs)
                    foreach(tz2; testTZs)
                        testCmp(st1, tz1, st2, tz2);

        auto st = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30));
        assert(st.opCmp(st) == 0);
        assert(st.opCmp(cst) == 0);
        //assert(st.opCmp(ist) == 0);
        assert(cst.opCmp(st) == 0);
        assert(cst.opCmp(cst) == 0);
        //assert(cst.opCmp(ist) == 0);
        //assert(ist.opCmp(st) == 0);
        //assert(ist.opCmp(cst) == 0);
        //assert(ist.opCmp(ist) == 0);
    }

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

    unittest
    {
        import std.range;
        static void test(SysTime sysTime, long expected)
        {
            import std.format : format;
            assert(sysTime.year == expected,
                             format("Value given: %s", sysTime));
        }

        test(SysTime(0, UTC()), 1);
        test(SysTime(1, UTC()), 1);
        test(SysTime(-1, UTC()), 0);

        foreach(year; chain(testYearsBC, testYearsAD))
        {
            foreach(md; testMonthDays)
            {
                foreach(tod; testTODs)
                {
                    auto dt = DateTime(Date(year, md.month, md.day), tod);

                    foreach(tz; testTZs)
                    {
                        foreach(fs; testFracSecs)
                            test(SysTime(dt, fs, tz), year);
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.year == 1999);
        //assert(ist.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 SysTime)'s year to.

        Throws:
            $(LREF DateTimeException) if the new year is not a leap year and the
            resulting date would be on February 29th.
     +/
    @property void year(int year) @safe
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        auto date = Date(cast(int)days);
        date.year = year;

        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
        adjTime = newDaysHNSecs + hnsecs;
    }

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

    unittest
    {
        import std.range;
        static void test(SysTime st, int year, in SysTime expected)
        {
            st.year = year;
            assert(st == expected);
        }

        foreach(st; chain(testSysTimesBC, testSysTimesAD))
        {
            auto dt = cast(DateTime)st;

            foreach(year; chain(testYearsBC, testYearsAD))
            {
                auto e = SysTime(DateTime(year, dt.month, dt.day, dt.hour, dt.minute, dt.second),
                                 st.fracSecs,
                                 st.timezone);
                test(st, year, e);
            }
        }

        foreach(fs; testFracSecs)
        {
            foreach(tz; testTZs)
            {
                foreach(tod; testTODs)
                {
                    test(SysTime(DateTime(Date(1999, 2, 28), tod), fs, tz), 2000,
                         SysTime(DateTime(Date(2000, 2, 28), tod), fs, tz));
                    test(SysTime(DateTime(Date(2000, 2, 28), tod), fs, tz), 1999,
                         SysTime(DateTime(Date(1999, 2, 28), tod), fs, tz));
                }

                foreach(tod; testTODsThrown)
                {
                    auto st = SysTime(DateTime(Date(2000, 2, 29), tod), fs, tz);
                    assertThrown!DateTimeException(st.year = 1999);
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.year = 7));
        //static assert(!__traits(compiles, ist.year = 7));
    }

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

        Throws:
            $(LREF DateTimeException) if $(D isAD) is true.
     +/
    @property ushort yearBC() @safe const
    {
        return (cast(Date)this).yearBC;
    }

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

    unittest
    {
        import std.format : format;
        foreach(st; testSysTimesBC)
        {
            auto msg = format("SysTime: %s", st);
            assertNotThrown!DateTimeException(st.yearBC, msg);
            assert(st.yearBC == (st.year * -1) + 1, msg);
        }

        foreach(st; [testSysTimesAD[0], testSysTimesAD[$/2], testSysTimesAD[$-1]])
            assertThrown!DateTimeException(st.yearBC, format("SysTime: %s", st));

        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        st.year = 12;
        assert(st.year == 12);
        static assert(!__traits(compiles, cst.year = 12));
        //static assert(!__traits(compiles, ist.year = 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 SysTime)'s year to.

        Throws:
            $(LREF DateTimeException) if a non-positive value is given.
     +/
    @property void yearBC(int year) @safe
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        auto date = Date(cast(int)days);
        date.yearBC = year;

        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
        adjTime = newDaysHNSecs + hnsecs;
    }

    unittest
    {
        auto st = SysTime(DateTime(2010, 1, 1, 7, 30, 0));
        st.yearBC = 1;
        assert(st == SysTime(DateTime(0, 1, 1, 7, 30, 0)));

        st.yearBC = 10;
        assert(st == SysTime(DateTime(-9, 1, 1, 7, 30, 0)));
    }

    unittest
    {
        import std.range;
        static void test(SysTime st, int year, in SysTime expected)
        {
            import std.format : format;
            st.yearBC = year;
            assert(st == expected, format("SysTime: %s", st));
        }

        foreach(st; chain(testSysTimesBC, testSysTimesAD))
        {
            auto dt = cast(DateTime)st;

            foreach(year; testYearsBC)
            {
                auto e = SysTime(DateTime(year, dt.month, dt.day, dt.hour, dt.minute, dt.second),
                                 st.fracSecs,
                                 st.timezone);
                test(st, (year * -1) + 1, e);
            }
        }

        foreach(st; [testSysTimesBC[0], testSysTimesBC[$ - 1],
                     testSysTimesAD[0], testSysTimesAD[$ - 1]])
        {
            foreach(year; testYearsBC)
                assertThrown!DateTimeException(st.yearBC = year);
        }

        foreach(fs; testFracSecs)
        {
            foreach(tz; testTZs)
            {
                foreach(tod; testTODs)
                {
                    test(SysTime(DateTime(Date(-1999, 2, 28), tod), fs, tz), 2001,
                         SysTime(DateTime(Date(-2000, 2, 28), tod), fs, tz));
                    test(SysTime(DateTime(Date(-2000, 2, 28), tod), fs, tz), 2000,
                         SysTime(DateTime(Date(-1999, 2, 28), tod), fs, tz));
                }

                foreach(tod; testTODsThrown)
                {
                    auto st = SysTime(DateTime(Date(-2000, 2, 29), tod), fs, tz);
                    assertThrown!DateTimeException(st.year = -1999);
                }
            }
        }

        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        st.yearBC = 12;
        assert(st.yearBC == 12);
        static assert(!__traits(compiles, cst.yearBC = 12));
        //static assert(!__traits(compiles, ist.yearBC = 12));
    }

    /++
        Month of a Gregorian Year.
     +/
    @property Month month() @safe const nothrow
    {
        return (cast(Date)this).month;
    }

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

    unittest
    {
        import std.range;
        static void test(SysTime sysTime, Month expected)
        {
            import std.format : format;
            assert(sysTime.month == expected,
                             format("Value given: %s", sysTime));
        }

        test(SysTime(0, UTC()), Month.jan);
        test(SysTime(1, UTC()), Month.jan);
        test(SysTime(-1, UTC()), Month.dec);

        foreach(year; chain(testYearsBC, testYearsAD))
        {
            foreach(md; testMonthDays)
            {
                foreach(tod; testTODs)
                {
                    auto dt = DateTime(Date(year, md.month, md.day), tod);

                    foreach(fs; testFracSecs)
                    {
                        foreach(tz; testTZs)
                            test(SysTime(dt, fs, tz), md.month);
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.month == 7);
        //assert(ist.month == 7);
    }


    /++
        Month of a Gregorian Year.

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

        Throws:
            $(LREF DateTimeException) if the given month is not a valid month.
     +/
    @property void month(Month month) @safe
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        auto date = Date(cast(int)days);
        date.month = month;

        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
        adjTime = newDaysHNSecs + hnsecs;
    }

    unittest
    {
        import std.range;

        static void test(SysTime st, Month month, in SysTime expected)
        {
            st.month = cast(Month)month;
            assert(st == expected);
        }

        foreach(st; chain(testSysTimesBC, testSysTimesAD))
        {
            auto dt = cast(DateTime)st;

            foreach(md; testMonthDays)
            {
                if(st.day > maxDay(dt.year, md.month))
                    continue;
                auto e = SysTime(DateTime(dt.year, md.month, dt.day, dt.hour, dt.minute, dt.second),
                                 st.fracSecs,
                                 st.timezone);
                test(st, md.month, e);
            }
        }

        foreach(fs; testFracSecs)
        {
            foreach(tz; testTZs)
            {
                foreach(tod; testTODs)
                {
                    foreach(year; filter!((a){return yearIsLeapYear(a);})
                                         (chain(testYearsBC, testYearsAD)))
                    {
                        test(SysTime(DateTime(Date(year, 1, 29), tod), fs, tz),
                             Month.feb,
                             SysTime(DateTime(Date(year, 2, 29), tod), fs, tz));
                    }

                    foreach(year; chain(testYearsBC, testYearsAD))
                    {
                        test(SysTime(DateTime(Date(year, 1, 28), tod), fs, tz),
                             Month.feb,
                             SysTime(DateTime(Date(year, 2, 28), tod), fs, tz));
                        test(SysTime(DateTime(Date(year, 7, 30), tod), fs, tz),
                             Month.jun,
                             SysTime(DateTime(Date(year, 6, 30), tod), fs, tz));
                    }
                }
            }
        }

        foreach(fs; [testFracSecs[0], testFracSecs[$-1]])
        {
            foreach(tz; testTZs)
            {
                foreach(tod; testTODsThrown)
                {
                    foreach(year; [testYearsBC[$-3], testYearsBC[$-2],
                                   testYearsBC[$-2], testYearsAD[0],
                                   testYearsAD[$-2], testYearsAD[$-1]])
                    {
                        auto day = yearIsLeapYear(year) ? 30 : 29;
                        auto st1 = SysTime(DateTime(Date(year, 1, day), tod), fs, tz);
                        assertThrown!DateTimeException(st1.month = Month.feb);

                        auto st2 = SysTime(DateTime(Date(year, 7, 31), tod), fs, tz);
                        assertThrown!DateTimeException(st2.month = Month.jun);
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.month = 12));
        //static assert(!__traits(compiles, ist.month = 12));
    }

    /++
        Day of a Gregorian Month.
     +/
    @property ubyte day() @safe const nothrow
    {
        return (cast(Date)this).day;
    }

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

    unittest
    {
        import std.range;

        static void test(SysTime sysTime, int expected)
        {
            import std.format : format;
            assert(sysTime.day == expected,
                             format("Value given: %s", sysTime));
        }

        test(SysTime(0, UTC()), 1);
        test(SysTime(1, UTC()), 1);
        test(SysTime(-1, UTC()), 31);

        foreach(year; chain(testYearsBC, testYearsAD))
        {
            foreach(md; testMonthDays)
            {
                foreach(tod; testTODs)
                {
                    auto dt = DateTime(Date(year, md.month, md.day), tod);

                    foreach(tz; testTZs)
                    {
                        foreach(fs; testFracSecs)
                            test(SysTime(dt, fs, tz), md.day);
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
         assert(cst.day == 6);
        //assert(ist.day == 6);
    }


    /++
        Day of a Gregorian Month.

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

        Throws:
            $(LREF DateTimeException) if the given day is not a valid day of the
            current month.
     +/
    @property void day(int day) @safe
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        auto date = Date(cast(int)days);
        date.day = day;

        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);
        adjTime = newDaysHNSecs + hnsecs;
    }

    unittest
    {
        import std.format : format;
        import std.range;

        foreach(day; chain(testDays))
        {
            foreach(st; chain(testSysTimesBC, testSysTimesAD))
            {
                auto dt = cast(DateTime)st;

                if(day > maxDay(dt.year, dt.month))
                    continue;
                auto expected = SysTime(DateTime(dt.year, dt.month, day, dt.hour, dt.minute, dt.second),
                                        st.fracSecs,
                                        st.timezone);
                st.day = day;
                assert(st == expected, format("[%s] [%s]", st, expected));
            }
        }

        foreach(tz; testTZs)
        {
            foreach(tod; testTODs)
            {
                foreach(fs; testFracSecs)
                {
                    foreach(year; chain(testYearsBC, testYearsAD))
                    {
                        foreach(month; EnumMembers!Month)
                        {
                            auto st = SysTime(DateTime(Date(year, month, 1), tod), fs, tz);
                            immutable max = maxDay(year, month);
                            auto expected = SysTime(DateTime(Date(year, month, max), tod), fs, tz);

                            st.day = max;
                            assert(st == expected, format("[%s] [%s]", st, expected));
                        }
                    }
                }
            }
        }

        foreach(tz; testTZs)
        {
            foreach(tod; testTODsThrown)
            {
                foreach(fs; [testFracSecs[0], testFracSecs[$-1]])
                {
                    foreach(year; [testYearsBC[$-3], testYearsBC[$-2],
                                   testYearsBC[$-2], testYearsAD[0],
                                   testYearsAD[$-2], testYearsAD[$-1]])
                    {
                        foreach(month; EnumMembers!Month)
                        {
                            auto st = SysTime(DateTime(Date(year, month, 1), tod), fs, tz);
                            immutable max = maxDay(year, month);

                            assertThrown!DateTimeException(st.day = max + 1);
                        }
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.day = 27));
        //static assert(!__traits(compiles, ist.day = 27));
    }


    /++
        Hours past midnight.
     +/
    @property ubyte hour() @safe const nothrow
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        return cast(ubyte)getUnitsFromHNSecs!"hours"(hnsecs);
    }

    unittest
    {
        import std.range;
        import std.format : format;

        static void test(SysTime sysTime, int expected)
        {
            assert(sysTime.hour == expected,
                             format("Value given: %s", sysTime));
        }

        test(SysTime(0, UTC()), 0);
        test(SysTime(1, UTC()), 0);
        test(SysTime(-1, UTC()), 23);

        foreach(tz; testTZs)
        {
            foreach(year; chain(testYearsBC, testYearsAD))
            {
                foreach(md; testMonthDays)
                {
                    foreach(hour; testHours)
                    {
                        foreach(minute; testMinSecs)
                        {
                            foreach(second; testMinSecs)
                            {
                                auto dt = DateTime(Date(year, md.month, md.day),
                                                   TimeOfDay(hour, minute, second));

                                foreach(fs; testFracSecs)
                                    test(SysTime(dt, fs, tz), hour);
                            }
                        }
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.hour == 12);
        //assert(ist.hour == 12);
    }


    /++
        Hours past midnight.

        Params:
            hour = The hours to set this $(LREF SysTime)'s hour to.

        Throws:
            $(LREF DateTimeException) if the given hour are not a valid hour of
            the day.
     +/
    @property void hour(int hour) @safe
    {
        enforceValid!"hours"(hour);

        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
        immutable daysHNSecs = convert!("days", "hnsecs")(days);
        immutable negative = hnsecs < 0;

        if(negative)
            hnsecs += convert!("hours", "hnsecs")(24);

        hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs);
        hnsecs += convert!("hours", "hnsecs")(hour);

        if(negative)
            hnsecs -= convert!("hours", "hnsecs")(24);

        adjTime = daysHNSecs + hnsecs;
    }

    unittest
    {
        import std.range;
        import std.format : format;

        foreach(hour; chain(testHours))
        {
            foreach(st; chain(testSysTimesBC, testSysTimesAD))
            {
                auto dt = cast(DateTime)st;
                auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, hour, dt.minute, dt.second),
                                        st.fracSecs,
                                        st.timezone);
                st.hour = hour;
                assert(st == expected, format("[%s] [%s]", st, expected));
            }
        }

        auto st = testSysTimesAD[0];
        assertThrown!DateTimeException(st.hour = -1);
        assertThrown!DateTimeException(st.hour = 60);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.hour = 27));
        //static assert(!__traits(compiles, ist.hour = 27));
    }


    /++
        Minutes past the current hour.
     +/
    @property ubyte minute() @safe const nothrow
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs);

        return cast(ubyte)getUnitsFromHNSecs!"minutes"(hnsecs);
    }

    unittest
    {
        import std.range;
        import std.format : format;

        static void test(SysTime sysTime, int expected)
        {
            assert(sysTime.minute == expected,
                             format("Value given: %s", sysTime));
        }

        test(SysTime(0, UTC()), 0);
        test(SysTime(1, UTC()), 0);
        test(SysTime(-1, UTC()), 59);

        foreach(tz; testTZs)
        {
            foreach(year; chain(testYearsBC, testYearsAD))
            {
                foreach(md; testMonthDays)
                {
                    foreach(hour; testHours)
                    {
                        foreach(minute; testMinSecs)
                        {
                            foreach(second; testMinSecs)
                            {
                                auto dt = DateTime(Date(year, md.month, md.day),
                                                   TimeOfDay(hour, minute, second));

                                foreach(fs; testFracSecs)
                                    test(SysTime(dt, fs, tz), minute);
                            }
                        }
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.minute == 30);
        //assert(ist.minute == 30);
    }


    /++
        Minutes past the current hour.

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

        Throws:
            $(LREF DateTimeException) if the given minute are not a valid minute
            of an hour.
     +/
    @property void minute(int minute) @safe
    {
        enforceValid!"minutes"(minute);

        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
        immutable daysHNSecs = convert!("days", "hnsecs")(days);
        immutable negative = hnsecs < 0;

        if(negative)
            hnsecs += convert!("hours", "hnsecs")(24);

        immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
        hnsecs = removeUnitsFromHNSecs!"minutes"(hnsecs);

        hnsecs += convert!("hours", "hnsecs")(hour);
        hnsecs += convert!("minutes", "hnsecs")(minute);

        if(negative)
            hnsecs -= convert!("hours", "hnsecs")(24);

        adjTime = daysHNSecs + hnsecs;
    }

    unittest
    {
        import std.range;
        import std.format : format;

        foreach(minute; testMinSecs)
        {
            foreach(st; chain(testSysTimesBC, testSysTimesAD))
            {
                auto dt = cast(DateTime)st;
                auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, dt.hour, minute, dt.second),
                                        st.fracSecs,
                                        st.timezone);
                st.minute = minute;
                assert(st == expected, format("[%s] [%s]", st, expected));
            }
        }

        auto st = testSysTimesAD[0];
        assertThrown!DateTimeException(st.minute = -1);
        assertThrown!DateTimeException(st.minute = 60);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.minute = 27));
        //static assert(!__traits(compiles, ist.minute = 27));
    }


    /++
        Seconds past the current minute.
     +/
    @property ubyte second() @safe const nothrow
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs);
        hnsecs = removeUnitsFromHNSecs!"minutes"(hnsecs);

        return cast(ubyte)getUnitsFromHNSecs!"seconds"(hnsecs);
    }

    unittest
    {
        import std.range;
        import std.format : format;

        static void test(SysTime sysTime, int expected)
        {
            assert(sysTime.second == expected,
                             format("Value given: %s", sysTime));
        }

        test(SysTime(0, UTC()), 0);
        test(SysTime(1, UTC()), 0);
        test(SysTime(-1, UTC()), 59);

        foreach(tz; testTZs)
        {
            foreach(year; chain(testYearsBC, testYearsAD))
            {
                foreach(md; testMonthDays)
                {
                    foreach(hour; testHours)
                    {
                        foreach(minute; testMinSecs)
                        {
                            foreach(second; testMinSecs)
                            {
                                auto dt = DateTime(Date(year, md.month, md.day),
                                                   TimeOfDay(hour, minute, second));

                                foreach(fs; testFracSecs)
                                    test(SysTime(dt, fs, tz), second);
                            }
                        }
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.second == 33);
        //assert(ist.second == 33);
    }


    /++
        Seconds past the current minute.

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

        Throws:
            $(LREF DateTimeException) if the given second are not a valid second
            of a minute.
     +/
    @property void second(int second) @safe
    {
        enforceValid!"seconds"(second);

        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
        immutable daysHNSecs = convert!("days", "hnsecs")(days);
        immutable negative = hnsecs < 0;

        if(negative)
            hnsecs += convert!("hours", "hnsecs")(24);

        immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
        immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
        hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs);

        hnsecs += convert!("hours", "hnsecs")(hour);
        hnsecs += convert!("minutes", "hnsecs")(minute);
        hnsecs += convert!("seconds", "hnsecs")(second);

        if(negative)
            hnsecs -= convert!("hours", "hnsecs")(24);

        adjTime = daysHNSecs + hnsecs;
    }

    unittest
    {
        import std.range;
        import std.format : format;

        foreach(second; testMinSecs)
        {
            foreach(st; chain(testSysTimesBC, testSysTimesAD))
            {
                auto dt = cast(DateTime)st;
                auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, second),
                                        st.fracSecs,
                                        st.timezone);
                st.second = second;
                assert(st == expected, format("[%s] [%s]", st, expected));
            }
        }

        auto st = testSysTimesAD[0];
        assertThrown!DateTimeException(st.second = -1);
        assertThrown!DateTimeException(st.second = 60);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.seconds = 27));
        //static assert(!__traits(compiles, ist.seconds = 27));
    }


    /++
        Fractional seconds past the second (i.e. the portion of a
        $(LREF SysTime) which is less than a second).
     +/
    @property Duration fracSecs() @safe const nothrow
    {
        auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime);

        if(hnsecs < 0)
            hnsecs += convert!("hours", "hnsecs")(24);

        return dur!"hnsecs"(removeUnitsFromHNSecs!"seconds"(hnsecs));
    }

    ///
    unittest
    {
        auto dt = DateTime(1982, 4, 1, 20, 59, 22);
        assert(SysTime(dt, msecs(213)).fracSecs == msecs(213));
        assert(SysTime(dt, usecs(5202)).fracSecs == usecs(5202));
        assert(SysTime(dt, hnsecs(1234567)).fracSecs == hnsecs(1234567));

        // SysTime and Duration both have a precision of hnsecs (100 ns),
        // so nsecs are going to be truncated.
        assert(SysTime(dt, nsecs(123456789)).fracSecs == nsecs(123456700));
    }

    unittest
    {
        import std.range;

        assert(SysTime(0, UTC()).fracSecs == Duration.zero);
        assert(SysTime(1, UTC()).fracSecs == hnsecs(1));
        assert(SysTime(-1, UTC()).fracSecs == hnsecs(9_999_999));

        foreach(tz; testTZs)
        {
            foreach(year; chain(testYearsBC, testYearsAD))
            {
                foreach(md; testMonthDays)
                {
                    foreach(hour; testHours)
                    {
                        foreach(minute; testMinSecs)
                        {
                            foreach(second; testMinSecs)
                            {
                                auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second));
                                foreach(fs; testFracSecs)
                                    assert(SysTime(dt, fs, tz).fracSecs == fs);
                            }
                        }
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.fracSecs == Duration.zero);
        //assert(ist.fracSecs == Duration.zero);
    }


    /++
        Fractional seconds past the second (i.e. the portion of a
        $(LREF SysTime) which is less than a second).

        Params:
            fracSecs = The duration to set this $(LREF SysTime)'s fractional
                       seconds to.

        Throws:
            $(LREF DateTimeException) if the given duration is negative or if
            it's greater than or equal to one second.
     +/
    @property void fracSecs(Duration fracSecs) @safe
    {
        enforce(fracSecs >= Duration.zero, new DateTimeException("A SysTime cannot have negative fractional seconds."));
        enforce(fracSecs < seconds(1), new DateTimeException("Fractional seconds must be less than one second."));

        auto oldHNSecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(oldHNSecs);
        immutable daysHNSecs = convert!("days", "hnsecs")(days);
        immutable negative = oldHNSecs < 0;

        if(negative)
            oldHNSecs += convert!("hours", "hnsecs")(24);

        immutable seconds = splitUnitsFromHNSecs!"seconds"(oldHNSecs);
        immutable secondsHNSecs = convert!("seconds", "hnsecs")(seconds);
        auto newHNSecs = fracSecs.total!"hnsecs" + secondsHNSecs;

        if(negative)
            newHNSecs -= convert!("hours", "hnsecs")(24);

        adjTime = daysHNSecs + newHNSecs;
    }

    ///
    unittest
    {
        auto st = SysTime(DateTime(1982, 4, 1, 20, 59, 22));
        assert(st.fracSecs == Duration.zero);

        st.fracSecs = msecs(213);
        assert(st.fracSecs == msecs(213));

        st.fracSecs = hnsecs(1234567);
        assert(st.fracSecs == hnsecs(1234567));

        // SysTime has a precision of hnsecs (100 ns), so nsecs are
        // going to be truncated.
        st.fracSecs = nsecs(123456789);
        assert(st.fracSecs == hnsecs(1234567));
    }

    unittest
    {
        import std.range;
        import std.format : format;

        foreach(fracSec; testFracSecs)
        {
            foreach(st; chain(testSysTimesBC, testSysTimesAD))
            {
                auto dt = cast(DateTime)st;
                auto expected = SysTime(dt, fracSec, st.timezone);
                st.fracSecs = fracSec;
                assert(st == expected, format("[%s] [%s]", st, expected));
            }
        }

        auto st = testSysTimesAD[0];
        assertThrown!DateTimeException(st.fracSecs = hnsecs(-1));
        assertThrown!DateTimeException(st.fracSecs = seconds(1));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.fracSecs = msecs(7)));
        //static assert(!__traits(compiles, ist.fracSecs = msecs(7)));
    }


    // @@@DEPRECATED_2016-08@@@
    /++
        $(RED Deprecated. Please use $(LREF fracSecs) instead of fracSec. It
              uses a $(CXREF time, Duration) to represent the fractional seconds
              instead of a $(CXREF time, FracSec). This overload will be removed
              in August 2016.)

        Fractional seconds past the second.
     +/
    deprecated("Please use fracSecs (with an s) rather than fracSec (without an s). It returns a Duration instead of a FracSec, as FracSec is being deprecated.")
    @property FracSec fracSec() @safe const nothrow
    {
        try
        {
            auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime);

            if(hnsecs < 0)
                hnsecs += convert!("hours", "hnsecs")(24);

            hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs);

            return FracSec.from!"hnsecs"(cast(int)hnsecs);
        }
        catch(Exception e)
            assert(0, "FracSec.from!\"hnsecs\"() threw.");
    }

    deprecated unittest
    {
        import std.range;
        import std.format : format;

        static void test(SysTime sysTime, FracSec expected, size_t line = __LINE__)
        {
            if(sysTime.fracSec != expected)
                throw new AssertError(format("Value given: %s", sysTime.fracSec), __FILE__, line);
        }

        test(SysTime(0, UTC()), FracSec.from!"hnsecs"(0));
        test(SysTime(1, UTC()), FracSec.from!"hnsecs"(1));
        test(SysTime(-1, UTC()), FracSec.from!"hnsecs"(9_999_999));

        foreach(tz; testTZs)
        {
            foreach(year; chain(testYearsBC, testYearsAD))
            {
                foreach(md; testMonthDays)
                {
                    foreach(hour; testHours)
                    {
                        foreach(minute; testMinSecs)
                        {
                            foreach(second; testMinSecs)
                            {
                                auto dt = DateTime(Date(year, md.month, md.day),
                                                   TimeOfDay(hour, minute, second));

                                foreach(fs; testFracSecs)
                                    test(SysTime(dt, fs, tz), FracSec.from!"hnsecs"(fs.total!"hnsecs"));
                            }
                        }
                    }
                }
            }
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.fracSec == FracSec.zero);
        //assert(ist.fracSec == FracSec.zero);
    }


    // @@@DEPRECATED_2016-08@@@
    /++
        $(RED Deprecated. Please use $(LREF fracSecs) instead of fracSec. It
              uses a $(CXREF time, Duration) to represent the fractional seconds
              instead of a $(CXREF time, FracSec). This overload will be removed
              in August 2016.)

        Fractional seconds past the second.

        Params:
            fracSec = The fractional seconds to set this $(LREF SysTime)'s
                      fractional seconds to.

        Throws:
            $(LREF DateTimeException) if $(D fracSec) is negative.
     +/
    deprecated("Please use fracSecs (with an s) rather than fracSec (without an s). It takes a Duration instead of a FracSec, as FracSec is being deprecated.")
    @property void fracSec(FracSec fracSec) @safe
    {
        immutable fracHNSecs = fracSec.hnsecs;
        enforce(fracHNSecs >= 0, new DateTimeException("A SysTime cannot have negative fractional seconds."));

        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs);
        immutable daysHNSecs = convert!("days", "hnsecs")(days);
        immutable negative = hnsecs < 0;

        if(negative)
            hnsecs += convert!("hours", "hnsecs")(24);

        immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
        immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
        immutable second = getUnitsFromHNSecs!"seconds"(hnsecs);

        hnsecs = fracHNSecs;
        hnsecs += convert!("hours", "hnsecs")(hour);
        hnsecs += convert!("minutes", "hnsecs")(minute);
        hnsecs += convert!("seconds", "hnsecs")(second);

        if(negative)
            hnsecs -= convert!("hours", "hnsecs")(24);

        adjTime = daysHNSecs + hnsecs;
    }

    deprecated unittest
    {
        import std.range;
        import std.format : format;

        foreach(fracSec; testFracSecs)
        {
            foreach(st; chain(testSysTimesBC, testSysTimesAD))
            {
                auto dt = cast(DateTime)st;
                auto expected = SysTime(dt, fracSec, st.timezone);
                st.fracSec = FracSec.from!"hnsecs"(fracSec.total!"hnsecs");
                assert(st == expected, format("[%s] [%s]", st, expected));
            }
        }

        auto st = testSysTimesAD[0];
        assertThrown!DateTimeException(st.fracSec = FracSec.from!"hnsecs"(-1));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.fracSec = FracSec.from!"msecs"(7)));
        //static assert(!__traits(compiles, ist.fracSec = FracSec.from!"msecs"(7)));
    }


    /++
        The total hnsecs from midnight, January 1st, 1 A.D. UTC. This is the
        internal representation of $(LREF SysTime).
     +/
    @property long stdTime() @safe const pure nothrow
    {
        return _stdTime;
    }

    unittest
    {
        assert(SysTime(0).stdTime == 0);
        assert(SysTime(1).stdTime == 1);
        assert(SysTime(-1).stdTime == -1);
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 33), hnsecs(502), UTC()).stdTime == 330_000_502L);
        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC()).stdTime == 621_355_968_000_000_000L);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.stdTime > 0);
        //assert(ist.stdTime > 0);
    }


    /++
        The total hnsecs from midnight, January 1st, 1 A.D. UTC. This is the
        internal representation of $(LREF SysTime).

        Params:
            stdTime = The number of hnsecs since January 1st, 1 A.D. UTC.
     +/
    @property void stdTime(long stdTime) @safe pure nothrow
    {
        _stdTime = stdTime;
    }

    unittest
    {
        static void test(long stdTime, in SysTime expected, size_t line = __LINE__)
        {
            auto st = SysTime(0, UTC());
            st.stdTime = stdTime;
            assert(st == expected);
        }

        test(0, SysTime(Date(1, 1, 1), UTC()));
        test(1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()));
        test(-1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()));
        test(330_000_502L, SysTime(DateTime(1, 1, 1, 0, 0, 33), hnsecs(502), UTC()));
        test(621_355_968_000_000_000L, SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC()));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.stdTime = 27));
        //static assert(!__traits(compiles, ist.stdTime = 27));
    }


    /++
        The current time zone of this $(LREF SysTime). Its internal time is always
        kept in UTC, so there are no conversion issues between time zones due to
        DST. Functions which return all or part of the time - such as hours -
        adjust the time to this $(LREF SysTime)'s time zone before returning.
      +/
    @property immutable(TimeZone) timezone() @safe const pure nothrow
    {
        return _timezone;
    }


    /++
        The current time zone of this $(LREF SysTime). It's internal time is always
        kept in UTC, so there are no conversion issues between time zones due to
        DST. Functions which return all or part of the time - such as hours -
        adjust the time to this $(LREF SysTime)'s time zone before returning.

        Params:
            timezone = The $(LREF2 .TimeZone, TimeZone) to set this $(LREF SysTime)'s time zone to.
      +/
    @property void timezone(immutable TimeZone timezone) @safe pure nothrow
    {
        if(timezone is null)
            _timezone = LocalTime();
        else
            _timezone = timezone;
    }


    /++
        Returns whether DST is in effect for this $(LREF SysTime).
      +/
    @property bool dstInEffect() @safe const nothrow
    {
        return _timezone.dstInEffect(_stdTime);
        //This function's unit testing is done in the time zone classes.
    }


    /++
        Returns what the offset from UTC is for this $(LREF SysTime).
        It includes the DST offset in effect at that time (if any).
      +/
    @property Duration utcOffset() @safe const nothrow
    {
        return _timezone.utcOffsetAt(_stdTime);
    }


    /++
        Returns a $(LREF SysTime) with the same std time as this one, but with
        $(LREF LocalTime) as its time zone.
      +/
    SysTime toLocalTime() @safe const pure nothrow
    {
        return SysTime(_stdTime, LocalTime());
    }

    unittest
    {
        {
            auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27));
            assert(sysTime == sysTime.toLocalTime());
            assert(sysTime._stdTime == sysTime.toLocalTime()._stdTime);
            assert(sysTime.toLocalTime().timezone is LocalTime());
            assert(sysTime.toLocalTime().timezone is sysTime.timezone);
            assert(sysTime.toLocalTime().timezone !is UTC());
        }

        {
            auto stz = new immutable SimpleTimeZone(dur!"minutes"(-3 * 60));
            auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27), stz);
            assert(sysTime == sysTime.toLocalTime());
            assert(sysTime._stdTime == sysTime.toLocalTime()._stdTime);
            assert(sysTime.toLocalTime().timezone is LocalTime());
            assert(sysTime.toLocalTime().timezone !is UTC());
            assert(sysTime.toLocalTime().timezone !is stz);
        }
    }


    /++
        Returns a $(LREF SysTime) with the same std time as this one, but with
        $(D UTC) as its time zone.
      +/
    SysTime toUTC() @safe const pure nothrow
    {
        return SysTime(_stdTime, UTC());
    }

    unittest
    {
        auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27));
        assert(sysTime == sysTime.toUTC());
        assert(sysTime._stdTime == sysTime.toUTC()._stdTime);
        assert(sysTime.toUTC().timezone is UTC());
        assert(sysTime.toUTC().timezone !is LocalTime());
        assert(sysTime.toUTC().timezone !is sysTime.timezone);
    }


    /++
        Returns a $(LREF SysTime) with the same std time as this one, but with
        given time zone as its time zone.
      +/
    SysTime toOtherTZ(immutable TimeZone tz) @safe const pure nothrow
    {
        if(tz is null)
            return SysTime(_stdTime, LocalTime());
        else
            return SysTime(_stdTime, tz);
    }

    unittest
    {
        auto stz = new immutable SimpleTimeZone(dur!"minutes"(11 * 60));
        auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27));
        assert(sysTime == sysTime.toOtherTZ(stz));
        assert(sysTime._stdTime == sysTime.toOtherTZ(stz)._stdTime);
        assert(sysTime.toOtherTZ(stz).timezone is stz);
        assert(sysTime.toOtherTZ(stz).timezone !is LocalTime());
        assert(sysTime.toOtherTZ(stz).timezone !is UTC());
    }


    /++
        Converts this $(LREF SysTime) to unix time (i.e. seconds from midnight,
        January 1st, 1970 in UTC).

        The C standard does not specify the representation of time_t, so it is
        implementation defined. On POSIX systems, unix time is equivalent to
        time_t, but that's not necessarily true on other systems (e.g. it is
        not true for the Digital Mars C runtime). So, be careful when using unix
        time with C functions on non-POSIX systems.

        By default, the return type is time_t (which is normally an alias for
        int on 32-bit systems and long on 64-bit systems), but if a different
        size is required than either int or long can be passed as a template
        argument to get the desired size.

        If the return type is int, and the result can't fit in an int, then the
        closest value that can be held in 32 bits will be used (so $(D int.max)
        if it goes over and $(D int.min) if it goes under). However, no attempt
        is made to deal with integer overflow if the return type is long.

        Params:
            T = The return type (int or long). It defaults to time_t, which is
                normally 32 bits on a 32-bit system and 64 bits on a 64-bit
                system.

        Returns:
            A signed integer representing the unix time which is equivalent to
            this SysTime.
      +/
    T toUnixTime(T = time_t)() @safe const pure nothrow
        if(is(T == int) || is(T == long))
    {
        return stdTimeToUnixTime!T(_stdTime);
    }

    ///
    unittest
    {
        assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0);

        auto pst = new immutable SimpleTimeZone(hours(-8));
        assert(SysTime(DateTime(1970, 1, 1), pst).toUnixTime() == 28800);

        auto utc = SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC());
        assert(utc.toUnixTime() == 1_198_311_285);

        auto ca = SysTime(DateTime(2007, 12, 22, 8, 14, 45), pst);
        assert(ca.toUnixTime() == 1_198_340_085);
    }

    unittest
    {
        assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0);
        import std.meta : AliasSeq;
        foreach(units; AliasSeq!("hnsecs", "usecs", "msecs"))
            assert(SysTime(DateTime(1970, 1, 1, 0, 0, 0), dur!units(1), UTC()).toUnixTime() == 0);
        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toUnixTime() == 1);
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toUnixTime() == 0);
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toUnixTime() == 0);
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toUnixTime() == 0);
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toUnixTime() == -1);
    }


    /++
        Converts from unix time (i.e. seconds from midnight, January 1st, 1970
        in UTC) to a $(LREF SysTime).

        The C standard does not specify the representation of time_t, so it is
        implementation defined. On POSIX systems, unix time is equivalent to
        time_t, but that's not necessarily true on other systems (e.g. it is
        not true for the Digital Mars C runtime). So, be careful when using unix
        time with C functions on non-POSIX systems.

        Params:
            unixTime = Seconds from midnight, January 1st, 1970 in UTC.
            tz = The time zone for the SysTime that's returned.
      +/
    static SysTime fromUnixTime(long unixTime, immutable TimeZone tz = LocalTime()) @safe pure nothrow
    {
        return SysTime(unixTimeToStdTime(unixTime), tz);
    }

    ///
    unittest
    {
        assert(SysTime.fromUnixTime(0) ==
               SysTime(DateTime(1970, 1, 1), UTC()));

        auto pst = new immutable SimpleTimeZone(hours(-8));
        assert(SysTime.fromUnixTime(28800) ==
               SysTime(DateTime(1970, 1, 1), pst));

        auto st1 = SysTime.fromUnixTime(1_198_311_285, UTC());
        assert(st1 == SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC()));
        assert(st1.timezone is UTC());
        assert(st1 == SysTime(DateTime(2007, 12, 22, 0, 14, 45), pst));

        auto st2 = SysTime.fromUnixTime(1_198_311_285, pst);
        assert(st2 == SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC()));
        assert(st2.timezone is pst);
        assert(st2 == SysTime(DateTime(2007, 12, 22, 0, 14, 45), pst));
    }

    unittest
    {
        assert(SysTime.fromUnixTime(0) == SysTime(DateTime(1970, 1, 1), UTC()));
        assert(SysTime.fromUnixTime(1) == SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()));
        assert(SysTime.fromUnixTime(-1) == SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()));

        auto st = SysTime.fromUnixTime(0);
        auto dt = cast(DateTime)st;
        assert(dt <= DateTime(1970, 2, 1) && dt >= DateTime(1969, 12, 31));
        assert(st.timezone is LocalTime());

        auto aest = new immutable SimpleTimeZone(hours(10));
        assert(SysTime.fromUnixTime(-36000) == SysTime(DateTime(1970, 1, 1), aest));
    }


    /++
        Returns a $(D timeval) which represents this $(LREF SysTime).

        Note that like all conversions in std.datetime, this is a truncating
        conversion.

        If $(D timeval.tv_sec) is int, and the result can't fit in an int, then
        the closest value that can be held in 32 bits will be used for
        $(D tv_sec). (so $(D int.max) if it goes over and $(D int.min) if it
        goes under).
      +/
    timeval toTimeVal() @safe const pure nothrow
    {
        immutable tv_sec = toUnixTime!(typeof(timeval.tv_sec))();
        immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L);
        immutable tv_usec = cast(typeof(timeval.tv_usec))convert!("hnsecs", "usecs")(fracHNSecs);
        return timeval(tv_sec, tv_usec);
    }

    unittest
    {
        assert(SysTime(DateTime(1970, 1, 1), UTC()).toTimeVal() == timeval(0, 0));
        assert(SysTime(DateTime(1970, 1, 1), hnsecs(9), UTC()).toTimeVal() == timeval(0, 0));
        assert(SysTime(DateTime(1970, 1, 1), hnsecs(10), UTC()).toTimeVal() == timeval(0, 1));
        assert(SysTime(DateTime(1970, 1, 1), usecs(7), UTC()).toTimeVal() == timeval(0, 7));

        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toTimeVal() == timeval(1, 0));
        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(9), UTC()).toTimeVal() == timeval(1, 0));
        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(10), UTC()).toTimeVal() == timeval(1, 1));
        assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), usecs(7), UTC()).toTimeVal() == timeval(1, 7));

        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toTimeVal() == timeval(0, 0));
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_990), UTC()).toTimeVal() == timeval(0, -1));

        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toTimeVal() == timeval(0, -1));
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999), UTC()).toTimeVal() == timeval(0, -999_001));
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toTimeVal() == timeval(0, -1000));
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toTimeVal() == timeval(-1, 0));
        assert(SysTime(DateTime(1969, 12, 31, 23, 59, 58), usecs(17), UTC()).toTimeVal() == timeval(-1, -999_983));
    }


    /++
        Returns a $(D tm) which represents this $(LREF SysTime).
      +/
    tm toTM() @safe const nothrow
    {
        auto dateTime = cast(DateTime)this;
        tm timeInfo;

        timeInfo.tm_sec = dateTime.second;
        timeInfo.tm_min = dateTime.minute;
        timeInfo.tm_hour = dateTime.hour;
        timeInfo.tm_mday = dateTime.day;
        timeInfo.tm_mon = dateTime.month - 1;
        timeInfo.tm_year = dateTime.year - 1900;
        timeInfo.tm_wday = dateTime.dayOfWeek;
        timeInfo.tm_yday = dateTime.dayOfYear - 1;
        timeInfo.tm_isdst = _timezone.dstInEffect(_stdTime);

        version(Posix)
        {
            import std.utf : toUTFz;
            timeInfo.tm_gmtoff = cast(int)convert!("hnsecs", "seconds")(adjTime - _stdTime);
            auto zone = (timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName);
            timeInfo.tm_zone = zone.toUTFz!(char*)();
        }

        return timeInfo;
    }

    unittest
    {
        import std.conv : to;
        version(Posix)
        {
            scope(exit) clearTZEnvVar();
            setTZEnvVar("America/Los_Angeles");
        }

        {
            auto timeInfo = SysTime(DateTime(1970, 1, 1)).toTM();

            assert(timeInfo.tm_sec == 0);
            assert(timeInfo.tm_min == 0);
            assert(timeInfo.tm_hour == 0);
            assert(timeInfo.tm_mday == 1);
            assert(timeInfo.tm_mon == 0);
            assert(timeInfo.tm_year == 70);
            assert(timeInfo.tm_wday == 4);
            assert(timeInfo.tm_yday == 0);

            version(Posix)
                assert(timeInfo.tm_isdst == 0);
            else version(Windows)
                assert(timeInfo.tm_isdst == 0 || timeInfo.tm_isdst == 1);

            version(Posix)
            {
                assert(timeInfo.tm_gmtoff == -8 * 60 * 60);
                assert(to!string(timeInfo.tm_zone) == "PST");
            }
        }

        {
            auto timeInfo = SysTime(DateTime(2010, 7, 4, 12, 15, 7), hnsecs(15)).toTM();

            assert(timeInfo.tm_sec == 7);
            assert(timeInfo.tm_min == 15);
            assert(timeInfo.tm_hour == 12);
            assert(timeInfo.tm_mday == 4);
            assert(timeInfo.tm_mon == 6);
            assert(timeInfo.tm_year == 110);
            assert(timeInfo.tm_wday == 0);
            assert(timeInfo.tm_yday == 184);

            version(Posix)
                assert(timeInfo.tm_isdst == 1);
            else version(Windows)
                assert(timeInfo.tm_isdst == 0 || timeInfo.tm_isdst == 1);

            version(Posix)
            {
                assert(timeInfo.tm_gmtoff == -7 * 60 * 60);
                assert(to!string(timeInfo.tm_zone) == "PDT");
            }
        }
    }


    /++
        Adds the given number of years or months to this $(LREF SysTime). 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 SysTime).
            allowOverflow = Whether the days should be allowed to overflow,
                            causing the month to increment.
      +/
    ref SysTime add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow
        if(units == "years" ||
           units == "months")
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        auto date = Date(cast(int)days);
        date.add!units(value, allowOverflow);
        days = date.dayOfGregorianCal - 1;

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

        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);

        adjTime = newDaysHNSecs + hnsecs;

        return this;
    }

    unittest
    {
        auto st1 = SysTime(DateTime(2010, 1, 1, 12, 30, 33));
        st1.add!"months"(11);
        assert(st1 == SysTime(DateTime(2010, 12, 1, 12, 30, 33)));

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 7, 3), msecs(234));
            sysTime.add!"years"(7);
            assert(sysTime == SysTime(DateTime(2006, 7, 6, 12, 7, 3), msecs(234)));
            sysTime.add!"years"(-9);
            assert(sysTime == SysTime(DateTime(1997, 7, 6, 12, 7, 3), msecs(234)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207));
            sysTime.add!"years"(1);
            assert(sysTime == SysTime(DateTime(2000, 2, 28, 0, 7, 2), usecs(1207)));
        }

        {
            auto sysTime = SysTime(DateTime(2000, 2, 29, 0, 7, 2), usecs(1207));
            sysTime.add!"years"(-1);
            assert(sysTime == SysTime(DateTime(1999, 3, 1, 0, 7, 2), usecs(1207)));
        }

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

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

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

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 7, 3), msecs(234));
            sysTime.add!"years"(-7);
            assert(sysTime == SysTime(DateTime(-2006, 7, 6, 12, 7, 3), msecs(234)));
            sysTime.add!"years"(9);
            assert(sysTime == SysTime(DateTime(-1997, 7, 6, 12, 7, 3), msecs(234)));
        }

        {
            auto sysTime = SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3));
            sysTime.add!"years"(-1);
            assert(sysTime == SysTime(DateTime(-2000, 2, 28, 3, 3, 3), hnsecs(3)));
        }

        {
            auto sysTime = SysTime(DateTime(-2000, 2, 29, 3, 3, 3), hnsecs(3));
            sysTime.add!"years"(1);
            assert(sysTime == SysTime(DateTime(-1999, 3, 1, 3, 3, 3), hnsecs(3)));
        }

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.add!"years"(-1);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
            sysTime.add!"years"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"years"(-1);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"years"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 1, 1, 0, 0, 0));
            sysTime.add!"years"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
            sysTime.add!"years"(-1);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"years"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"years"(-1);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329));
            sysTime.add!"years"(-5);
            assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329)));
            sysTime.add!"years"(5);
            assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)));
        }

        {
            auto sysTime = SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329));
            sysTime.add!"years"(5);
            assert(sysTime == SysTime(DateTime(1, 7, 6, 14, 7, 1), usecs(54329)));
            sysTime.add!"years"(-5);
            assert(sysTime == SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329)));
        }

        {
            auto sysTime = SysTime(DateTime(-4, 2, 29, 5, 5, 5), msecs(555));
            sysTime.add!"years"(5);
            assert(sysTime == SysTime(DateTime(1, 3, 1, 5, 5, 5), msecs(555)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
            sysTime.add!"years"(-5);
            assert(sysTime == SysTime(DateTime(-1, 3, 1, 5, 5, 5), msecs(555)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
            sysTime.add!"years"(-5).add!"years"(7);
            assert(sysTime == SysTime(DateTime(6, 3, 1, 5, 5, 5), msecs(555)));
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.add!"years"(4)));
        //static assert(!__traits(compiles, ist.add!"years"(4)));
    }

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 7, 3), msecs(234));
            sysTime.add!"years"(7, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(2006, 7, 6, 12, 7, 3), msecs(234)));
            sysTime.add!"years"(-9, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1997, 7, 6, 12, 7, 3), msecs(234)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207));
            sysTime.add!"years"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(2000, 2, 28, 0, 7, 2), usecs(1207)));
        }

        {
            auto sysTime = SysTime(DateTime(2000, 2, 29, 0, 7, 2), usecs(1207));
            sysTime.add!"years"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207)));
        }

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

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

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

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 7, 3), msecs(234));
            sysTime.add!"years"(-7, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2006, 7, 6, 12, 7, 3), msecs(234)));
            sysTime.add!"years"(9, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1997, 7, 6, 12, 7, 3), msecs(234)));
        }

        {
            auto sysTime = SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3));
            sysTime.add!"years"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2000, 2, 28, 3, 3, 3), hnsecs(3)));
        }

        {
            auto sysTime = SysTime(DateTime(-2000, 2, 29, 3, 3, 3), hnsecs(3));
            sysTime.add!"years"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3)));
        }

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.add!"years"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
            sysTime.add!"years"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"years"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"years"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 1, 1, 0, 0, 0));
            sysTime.add!"years"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
            sysTime.add!"years"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"years"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"years"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329));
            sysTime.add!"years"(-5);
            assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329)));
            sysTime.add!"years"(5);
            assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329));
            sysTime.add!"years"(-5, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329)));
            sysTime.add!"years"(5, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)));
        }

        {
            auto sysTime = SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329));
            sysTime.add!"years"(5, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 7, 6, 14, 7, 1), usecs(54329)));
            sysTime.add!"years"(-5, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329)));
        }

        {
            auto sysTime = SysTime(DateTime(-4, 2, 29, 5, 5, 5), msecs(555));
            sysTime.add!"years"(5, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 2, 28, 5, 5, 5), msecs(555)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
            sysTime.add!"years"(-5, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1, 2, 28, 5, 5, 5), msecs(555)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555));
            sysTime.add!"years"(-5, AllowDayOverflow.no).add!"years"(7, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(6, 2, 28, 5, 5, 5), msecs(555)));
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.add!"months"(3);
            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.add!"months"(-4);
            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14);
            assert(sysTime == SysTime(DateTime(2000, 3, 2, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14);
            assert(sysTime == SysTime(DateTime(1999, 1, 2, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14);
            assert(sysTime == SysTime(DateTime(2001, 3, 3, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14);
            assert(sysTime == SysTime(DateTime(2000, 1, 3, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.add!"months"(3);
            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.add!"months"(-4);
            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14);
            assert(sysTime == SysTime(DateTime(-2000, 3, 2, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14);
            assert(sysTime == SysTime(DateTime(-2001, 1, 2, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14);
            assert(sysTime == SysTime(DateTime(-1999, 3, 3, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14);
            assert(sysTime == SysTime(DateTime(-2000, 1, 3, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.add!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
            sysTime.add!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
            sysTime.add!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
            sysTime.add!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
            sysTime.add!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 7, 9), hnsecs(17)));
            sysTime.add!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
            sysTime.add!"months"(-85);
            assert(sysTime == SysTime(DateTime(-3, 3, 3, 12, 11, 10), msecs(9)));
            sysTime.add!"months"(85);
            assert(sysTime == SysTime(DateTime(4, 4, 3, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.add!"months"(85);
            assert(sysTime == SysTime(DateTime(4, 5, 1, 12, 11, 10), msecs(9)));
            sysTime.add!"months"(-85);
            assert(sysTime == SysTime(DateTime(-3, 4, 1, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.add!"months"(85).add!"months"(-83);
            assert(sysTime == SysTime(DateTime(-3, 6, 1, 12, 11, 10), msecs(9)));
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.add!"months"(4)));
        //static assert(!__traits(compiles, ist.add!"months"(4)));
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.add!"months"(3, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.add!"months"(-4, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(2000, 2, 29, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1998, 12, 29, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(2001, 2, 28, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 12, 28, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.add!"months"(3, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.add!"months"(-4, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2000, 2, 29, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2002, 12, 29, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.add!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1999, 2, 28, 7, 7, 7), hnsecs(422202)));
            sysTime.add!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2001, 12, 28, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.add!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
            sysTime.add!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
            sysTime.add!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
            sysTime.add!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.add!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.add!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
            sysTime.add!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 7, 9), hnsecs(17)));
            sysTime.add!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
            sysTime.add!"months"(-85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-3, 2, 28, 12, 11, 10), msecs(9)));
            sysTime.add!"months"(85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(4, 3, 28, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.add!"months"(85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(4, 4, 30, 12, 11, 10), msecs(9)));
            sysTime.add!"months"(-85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-3, 3, 30, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.add!"months"(85, AllowDayOverflow.no).add!"months"(-83, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-3, 5, 30, 12, 11, 10), msecs(9)));
        }
    }


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

        The difference between rolling and adding is that rolling does not
        affect larger units. Rolling a $(LREF SysTime) 12 months
        gets the exact same $(LREF SysTime). 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 SysTime).
            allowOverflow = Whether the days should be allowed to overflow,
                            causing the month to increment.
      +/
    ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow
        if(units == "years")
    {
        return add!"years"(value, allowOverflow);
    }

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

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

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

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

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

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

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        st.roll!"years"(4);
        static assert(!__traits(compiles, cst.roll!"years"(4)));
        //static assert(!__traits(compiles, ist.roll!"years"(4)));
    }


    //Shares documentation with "years" overload.
    ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow
        if(units == "months")
    {
        auto hnsecs = adjTime;
        auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

        auto date = Date(cast(int)days);
        date.roll!"months"(value, allowOverflow);
        days = date.dayOfGregorianCal - 1;

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

        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
        adjTime = newDaysHNSecs + hnsecs;
        return this;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.roll!"months"(3);
            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.roll!"months"(-4);
            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14);
            assert(sysTime == SysTime(DateTime(1998, 3, 3, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14);
            assert(sysTime == SysTime(DateTime(1998, 1, 3, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14);
            assert(sysTime == SysTime(DateTime(1999, 3, 3, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14);
            assert(sysTime == SysTime(DateTime(1999, 1, 3, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.roll!"months"(-1);
            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 0, 0)));
            sysTime.roll!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"months"(-1);
            assert(sysTime == SysTime(DateTime(1, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
            sysTime.roll!"months"(1);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
            sysTime.roll!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"months"(1);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"months"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), hnsecs(5007));
            sysTime.roll!"months"(3);
            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), hnsecs(5007)));
            sysTime.roll!"months"(-4);
            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), hnsecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14);
            assert(sysTime == SysTime(DateTime(-2002, 3, 3, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14);
            assert(sysTime == SysTime(DateTime(-2002, 1, 3, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14);
            assert(sysTime == SysTime(DateTime(-2001, 3, 3, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14);
            assert(sysTime == SysTime(DateTime(-2001, 1, 3, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
            sysTime.roll!"months"(-1);
            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 7, 9), hnsecs(17)));
            sysTime.roll!"months"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
            sysTime.roll!"months"(-85);
            assert(sysTime == SysTime(DateTime(4, 3, 2, 12, 11, 10), msecs(9)));
            sysTime.roll!"months"(85);
            assert(sysTime == SysTime(DateTime(4, 4, 2, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.roll!"months"(85);
            assert(sysTime == SysTime(DateTime(-3, 5, 1, 12, 11, 10), msecs(9)));
            sysTime.roll!"months"(-85);
            assert(sysTime == SysTime(DateTime(-3, 4, 1, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.roll!"months"(85).roll!"months"(-83);
            assert(sysTime == SysTime(DateTime(-3, 6, 1, 12, 11, 10), msecs(9)));
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.roll!"months"(4)));
        //static assert(!__traits(compiles, ist.roll!"months"(4)));
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.roll!"months"(3, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.roll!"months"(-4, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1998, 2, 28, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1998, 12, 28, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 2, 28, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1999, 12, 28, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007));
            sysTime.roll!"months"(3, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007)));
            sysTime.roll!"months"(-4, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007)));
        }

        {
            auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2002, 2, 28, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2002, 12, 28, 7, 7, 7), hnsecs(422202)));
        }

        {
            auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202));
            sysTime.roll!"months"(14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2001, 2, 28, 7, 7, 7), hnsecs(422202)));
            sysTime.roll!"months"(-14, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-2001, 12, 28, 7, 7, 7), hnsecs(422202)));
        }

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.roll!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 0, 0)));
            sysTime.roll!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0));
            sysTime.roll!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0)));
            sysTime.roll!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17));
            sysTime.roll!"months"(-1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 7, 9), hnsecs(17)));
            sysTime.roll!"months"(1, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)));
        }

        {
            auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9));
            sysTime.roll!"months"(-85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(4, 2, 29, 12, 11, 10), msecs(9)));
            sysTime.roll!"months"(85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(4, 3, 29, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.roll!"months"(85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-3, 4, 30, 12, 11, 10), msecs(9)));
            sysTime.roll!"months"(-85, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-3, 3, 30, 12, 11, 10), msecs(9)));
        }

        {
            auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9));
            sysTime.roll!"months"(85, AllowDayOverflow.no).roll!"months"(-83, AllowDayOverflow.no);
            assert(sysTime == SysTime(DateTime(-3, 5, 30, 12, 11, 10), msecs(9)));
        }
    }


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

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

        Accepted units are $(D "days"), $(D "minutes"), $(D "hours"),
        $(D "minutes"), $(D "seconds"), $(D "msecs"), $(D "usecs"), and
        $(D "hnsecs").

        Note that when rolling msecs, usecs or hnsecs, they all add up to a
        second. So, for example, rolling 1000 msecs is exactly the same as
        rolling 100,000 usecs.

        Params:
            units = The units to add.
            value = The number of $(D_PARAM units) to add to this $(LREF SysTime).
      +/
    ref SysTime roll(string units)(long value) @safe nothrow
        if(units == "days")
    {
        auto hnsecs = adjTime;
        auto gdays = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

        if(hnsecs < 0)
        {
            hnsecs += convert!("hours", "hnsecs")(24);
            --gdays;
        }

        auto date = Date(cast(int)gdays);
        date.roll!"days"(value);
        gdays = date.dayOfGregorianCal - 1;

        if(gdays < 0)
        {
            hnsecs -= convert!("hours", "hnsecs")(24);
            ++gdays;
        }

        immutable newDaysHNSecs = convert!("days", "hnsecs")(gdays);
        adjTime = newDaysHNSecs + hnsecs;
        return  this;
    }

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

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

        auto st3 = SysTime(DateTime(2010, 2, 12, 12, 0, 0));
        st3.roll!"hours"(-1);
        assert(st3 == SysTime(DateTime(2010, 2, 12, 11, 0, 0)));

        auto st4 = SysTime(DateTime(2009, 12, 31, 0, 0, 0));
        st4.roll!"minutes"(1);
        assert(st4 == SysTime(DateTime(2009, 12, 31, 0, 1, 0)));

        auto st5 = SysTime(DateTime(2010, 1, 1, 0, 0, 0));
        st5.roll!"minutes"(-1);
        assert(st5 == SysTime(DateTime(2010, 1, 1, 0, 59, 0)));

        auto st6 = SysTime(DateTime(2009, 12, 31, 0, 0, 0));
        st6.roll!"seconds"(1);
        assert(st6 == SysTime(DateTime(2009, 12, 31, 0, 0, 1)));

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

        auto dt = DateTime(2010, 1, 1, 0, 0, 0);
        auto st8 = SysTime(dt);
        st8.roll!"msecs"(1);
        assert(st8 == SysTime(dt, msecs(1)));

        auto st9 = SysTime(dt);
        st9.roll!"msecs"(-1);
        assert(st9 == SysTime(dt, msecs(999)));

        auto st10 = SysTime(dt);
        st10.roll!"hnsecs"(1);
        assert(st10 == SysTime(dt, hnsecs(1)));

        auto st11 = SysTime(dt);
        st11.roll!"hnsecs"(-1);
        assert(st11 == SysTime(dt, hnsecs(9_999_999)));
    }

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

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(1999, 2, 28, 7, 9, 2), usecs(234578));
            sysTime.roll!"days"(1);
            assert(sysTime == SysTime(DateTime(1999, 2, 1, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(-1);
            assert(sysTime == SysTime(DateTime(1999, 2, 28, 7, 9, 2), usecs(234578)));
        }

        {
            auto sysTime = SysTime(DateTime(1999, 7, 6, 7, 9, 2), usecs(234578));
            sysTime.roll!"days"(9);
            assert(sysTime == SysTime(DateTime(1999, 7, 15, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(-11);
            assert(sysTime == SysTime(DateTime(1999, 7, 4, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(30);
            assert(sysTime == SysTime(DateTime(1999, 7, 3, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(-3);
            assert(sysTime == SysTime(DateTime(1999, 7, 31, 7, 9, 2), usecs(234578)));
        }

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

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

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

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

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

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

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

        {
            auto sysTime = SysTime(DateTime(-1999, 2, 28, 7, 9, 2), usecs(234578));
            sysTime.roll!"days"(1);
            assert(sysTime == SysTime(DateTime(-1999, 2, 1, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(-1);
            assert(sysTime == SysTime(DateTime(-1999, 2, 28, 7, 9, 2), usecs(234578)));
        }

        {
            auto sysTime = SysTime(DateTime(-1999, 7, 6, 7, 9, 2), usecs(234578));
            sysTime.roll!"days"(9);
            assert(sysTime == SysTime(DateTime(-1999, 7, 15, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(-11);
            assert(sysTime == SysTime(DateTime(-1999, 7, 4, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(30);
            assert(sysTime == SysTime(DateTime(-1999, 7, 3, 7, 9, 2), usecs(234578)));
            sysTime.roll!"days"(-3);
        }

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

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.roll!"days"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 31, 0, 0, 0)));
            sysTime.roll!"days"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"days"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 31, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"days"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 0, 0, 0));
            sysTime.roll!"days"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0)));
            sysTime.roll!"days"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"days"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"days"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 7, 6, 13, 13, 9), msecs(22));
            sysTime.roll!"days"(-365);
            assert(sysTime == SysTime(DateTime(1, 7, 13, 13, 13, 9), msecs(22)));
            sysTime.roll!"days"(365);
            assert(sysTime == SysTime(DateTime(1, 7, 6, 13, 13, 9), msecs(22)));
            sysTime.roll!"days"(-731);
            assert(sysTime == SysTime(DateTime(1, 7, 19, 13, 13, 9), msecs(22)));
            sysTime.roll!"days"(730);
            assert(sysTime == SysTime(DateTime(1, 7, 5, 13, 13, 9), msecs(22)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22));
            sysTime.roll!"days"(-365);
            assert(sysTime == SysTime(DateTime(0, 7, 13, 13, 13, 9), msecs(22)));
            sysTime.roll!"days"(365);
            assert(sysTime == SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22)));
            sysTime.roll!"days"(-731);
            assert(sysTime == SysTime(DateTime(0, 7, 19, 13, 13, 9), msecs(22)));
            sysTime.roll!"days"(730);
            assert(sysTime == SysTime(DateTime(0, 7, 5, 13, 13, 9), msecs(22)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22));
            sysTime.roll!"days"(-365).roll!"days"(362).roll!"days"(-12).roll!"days"(730);
            assert(sysTime == SysTime(DateTime(0, 7, 8, 13, 13, 9), msecs(22)));
        }

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


    //Shares documentation with "days" version.
    ref SysTime roll(string units)(long value) @safe nothrow
        if(units == "hours" ||
           units == "minutes" ||
           units == "seconds")
    {
        try
        {
            auto hnsecs = adjTime;
            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

            immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
            immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
            immutable second = splitUnitsFromHNSecs!"seconds"(hnsecs);

            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
            dateTime.roll!units(value);
            --days;

            hnsecs += convert!("hours", "hnsecs")(dateTime.hour);
            hnsecs += convert!("minutes", "hnsecs")(dateTime.minute);
            hnsecs += convert!("seconds", "hnsecs")(dateTime.second);

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

            immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
            adjTime = newDaysHNSecs + hnsecs;
            return this;
        }
        catch(Exception e)
            assert(0, "Either DateTime's constructor or TimeOfDay's constructor threw.");
    }

    //Test roll!"hours"().
    unittest
    {
        static void testST(SysTime orig, int hours, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            orig.roll!"hours"(hours);
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        immutable d = msecs(45);
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d);
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d));
        testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 15, 30, 33), d));
        testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 16, 30, 33), d));
        testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 17, 30, 33), d));
        testST(beforeAD, 6, SysTime(DateTime(1999, 7, 6, 18, 30, 33), d));
        testST(beforeAD, 7, SysTime(DateTime(1999, 7, 6, 19, 30, 33), d));
        testST(beforeAD, 8, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d));
        testST(beforeAD, 9, SysTime(DateTime(1999, 7, 6, 21, 30, 33), d));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d));
        testST(beforeAD, 11, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
        testST(beforeAD, 12, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
        testST(beforeAD, 13, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d));
        testST(beforeAD, 14, SysTime(DateTime(1999, 7, 6, 2, 30, 33), d));
        testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 3, 30, 33), d));
        testST(beforeAD, 16, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d));
        testST(beforeAD, 17, SysTime(DateTime(1999, 7, 6, 5, 30, 33), d));
        testST(beforeAD, 18, SysTime(DateTime(1999, 7, 6, 6, 30, 33), d));
        testST(beforeAD, 19, SysTime(DateTime(1999, 7, 6, 7, 30, 33), d));
        testST(beforeAD, 20, SysTime(DateTime(1999, 7, 6, 8, 30, 33), d));
        testST(beforeAD, 21, SysTime(DateTime(1999, 7, 6, 9, 30, 33), d));
        testST(beforeAD, 22, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d));
        testST(beforeAD, 23, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d));
        testST(beforeAD, 24, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 25, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d));
        testST(beforeAD, 50, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d));
        testST(beforeAD, 10_000, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d));

        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d));
        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d));
        testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 9, 30, 33), d));
        testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 8, 30, 33), d));
        testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 7, 30, 33), d));
        testST(beforeAD, -6, SysTime(DateTime(1999, 7, 6, 6, 30, 33), d));
        testST(beforeAD, -7, SysTime(DateTime(1999, 7, 6, 5, 30, 33), d));
        testST(beforeAD, -8, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d));
        testST(beforeAD, -9, SysTime(DateTime(1999, 7, 6, 3, 30, 33), d));
        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 2, 30, 33), d));
        testST(beforeAD, -11, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d));
        testST(beforeAD, -12, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
        testST(beforeAD, -13, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
        testST(beforeAD, -14, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d));
        testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 21, 30, 33), d));
        testST(beforeAD, -16, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d));
        testST(beforeAD, -17, SysTime(DateTime(1999, 7, 6, 19, 30, 33), d));
        testST(beforeAD, -18, SysTime(DateTime(1999, 7, 6, 18, 30, 33), d));
        testST(beforeAD, -19, SysTime(DateTime(1999, 7, 6, 17, 30, 33), d));
        testST(beforeAD, -20, SysTime(DateTime(1999, 7, 6, 16, 30, 33), d));
        testST(beforeAD, -21, SysTime(DateTime(1999, 7, 6, 15, 30, 33), d));
        testST(beforeAD, -22, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d));
        testST(beforeAD, -23, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d));
        testST(beforeAD, -24, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, -25, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d));
        testST(beforeAD, -50, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d));
        testST(beforeAD, -10_000, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d));

        testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), 1, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), 0, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), -1, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));

        testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), 1, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), 0, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), -1, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d));

        testST(SysTime(DateTime(1999, 7, 31, 23, 30, 33), d), 1, SysTime(DateTime(1999, 7, 31, 0, 30, 33), d));
        testST(SysTime(DateTime(1999, 8, 1, 0, 30, 33), d), -1, SysTime(DateTime(1999, 8, 1, 23, 30, 33), d));

        testST(SysTime(DateTime(1999, 12, 31, 23, 30, 33), d), 1, SysTime(DateTime(1999, 12, 31, 0, 30, 33), d));
        testST(SysTime(DateTime(2000, 1, 1, 0, 30, 33), d), -1, SysTime(DateTime(2000, 1, 1, 23, 30, 33), d));

        testST(SysTime(DateTime(1999, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(1999, 2, 28, 0, 30, 33), d));
        testST(SysTime(DateTime(1999, 3, 2, 0, 30, 33), d), -25, SysTime(DateTime(1999, 3, 2, 23, 30, 33), d));

        testST(SysTime(DateTime(2000, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(2000, 2, 28, 0, 30, 33), d));
        testST(SysTime(DateTime(2000, 3, 1, 0, 30, 33), d), -25, SysTime(DateTime(2000, 3, 1, 23, 30, 33), d));

        //Test B.C.
        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d);
        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d));
        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d));
        testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 15, 30, 33), d));
        testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 16, 30, 33), d));
        testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 17, 30, 33), d));
        testST(beforeBC, 6, SysTime(DateTime(-1999, 7, 6, 18, 30, 33), d));
        testST(beforeBC, 7, SysTime(DateTime(-1999, 7, 6, 19, 30, 33), d));
        testST(beforeBC, 8, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d));
        testST(beforeBC, 9, SysTime(DateTime(-1999, 7, 6, 21, 30, 33), d));
        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d));
        testST(beforeBC, 11, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
        testST(beforeBC, 12, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
        testST(beforeBC, 13, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d));
        testST(beforeBC, 14, SysTime(DateTime(-1999, 7, 6, 2, 30, 33), d));
        testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 3, 30, 33), d));
        testST(beforeBC, 16, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d));
        testST(beforeBC, 17, SysTime(DateTime(-1999, 7, 6, 5, 30, 33), d));
        testST(beforeBC, 18, SysTime(DateTime(-1999, 7, 6, 6, 30, 33), d));
        testST(beforeBC, 19, SysTime(DateTime(-1999, 7, 6, 7, 30, 33), d));
        testST(beforeBC, 20, SysTime(DateTime(-1999, 7, 6, 8, 30, 33), d));
        testST(beforeBC, 21, SysTime(DateTime(-1999, 7, 6, 9, 30, 33), d));
        testST(beforeBC, 22, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d));
        testST(beforeBC, 23, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d));
        testST(beforeBC, 24, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, 25, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d));
        testST(beforeBC, 50, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d));
        testST(beforeBC, 10_000, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d));

        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d));
        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d));
        testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 9, 30, 33), d));
        testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 8, 30, 33), d));
        testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 7, 30, 33), d));
        testST(beforeBC, -6, SysTime(DateTime(-1999, 7, 6, 6, 30, 33), d));
        testST(beforeBC, -7, SysTime(DateTime(-1999, 7, 6, 5, 30, 33), d));
        testST(beforeBC, -8, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d));
        testST(beforeBC, -9, SysTime(DateTime(-1999, 7, 6, 3, 30, 33), d));
        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 2, 30, 33), d));
        testST(beforeBC, -11, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d));
        testST(beforeBC, -12, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
        testST(beforeBC, -13, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
        testST(beforeBC, -14, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d));
        testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 21, 30, 33), d));
        testST(beforeBC, -16, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d));
        testST(beforeBC, -17, SysTime(DateTime(-1999, 7, 6, 19, 30, 33), d));
        testST(beforeBC, -18, SysTime(DateTime(-1999, 7, 6, 18, 30, 33), d));
        testST(beforeBC, -19, SysTime(DateTime(-1999, 7, 6, 17, 30, 33), d));
        testST(beforeBC, -20, SysTime(DateTime(-1999, 7, 6, 16, 30, 33), d));
        testST(beforeBC, -21, SysTime(DateTime(-1999, 7, 6, 15, 30, 33), d));
        testST(beforeBC, -22, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d));
        testST(beforeBC, -23, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d));
        testST(beforeBC, -24, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, -25, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d));
        testST(beforeBC, -50, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d));
        testST(beforeBC, -10_000, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d));

        testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));

        testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d));

        testST(SysTime(DateTime(-1999, 7, 31, 23, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 31, 0, 30, 33), d));
        testST(SysTime(DateTime(-1999, 8, 1, 0, 30, 33), d), -1, SysTime(DateTime(-1999, 8, 1, 23, 30, 33), d));

        testST(SysTime(DateTime(-2001, 12, 31, 23, 30, 33), d), 1, SysTime(DateTime(-2001, 12, 31, 0, 30, 33), d));
        testST(SysTime(DateTime(-2000, 1, 1, 0, 30, 33), d), -1, SysTime(DateTime(-2000, 1, 1, 23, 30, 33), d));

        testST(SysTime(DateTime(-2001, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(-2001, 2, 28, 0, 30, 33), d));
        testST(SysTime(DateTime(-2001, 3, 2, 0, 30, 33), d), -25, SysTime(DateTime(-2001, 3, 2, 23, 30, 33), d));

        testST(SysTime(DateTime(-2000, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(-2000, 2, 28, 0, 30, 33), d));
        testST(SysTime(DateTime(-2000, 3, 1, 0, 30, 33), d), -25, SysTime(DateTime(-2000, 3, 1, 23, 30, 33), d));

        //Test Both
        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 17_546, SysTime(DateTime(-1, 1, 1, 13, 30, 33), d));
        testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -17_546, SysTime(DateTime(1, 1, 1, 11, 30, 33), d));

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.roll!"hours"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 0, 0)));
            sysTime.roll!"hours"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"hours"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"hours"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 0, 0));
            sysTime.roll!"hours"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 0, 0)));
            sysTime.roll!"hours"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"hours"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"hours"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"hours"(1).roll!"hours"(-67);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 5, 59, 59), hnsecs(9_999_999)));
        }

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

    //Test roll!"minutes"().
    unittest
    {
        static void testST(SysTime orig, int minutes, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            orig.roll!"minutes"(minutes);
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        immutable d = usecs(7203);
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d);
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 32, 33), d));
        testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 12, 33, 33), d));
        testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 12, 34, 33), d));
        testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 12, 35, 33), d));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 40, 33), d));
        testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d));
        testST(beforeAD, 29, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
        testST(beforeAD, 30, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
        testST(beforeAD, 45, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d));
        testST(beforeAD, 60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 75, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d));
        testST(beforeAD, 90, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 10, 33), d));

        testST(beforeAD, 689, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
        testST(beforeAD, 690, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
        testST(beforeAD, 691, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
        testST(beforeAD, 960, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 1439, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d));
        testST(beforeAD, 1440, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 1441, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d));
        testST(beforeAD, 2880, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));

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

        testST(beforeAD, -749, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
        testST(beforeAD, -750, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
        testST(beforeAD, -751, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));
        testST(beforeAD, -960, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, -1439, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d));
        testST(beforeAD, -1440, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, -1441, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d));
        testST(beforeAD, -2880, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));

        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), 1, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), 0, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), -1, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d));

        testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), 1, SysTime(DateTime(1999, 7, 6, 11, 0, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), 0, SysTime(DateTime(1999, 7, 6, 11, 59, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), -1, SysTime(DateTime(1999, 7, 6, 11, 58, 33), d));

        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), 1, SysTime(DateTime(1999, 7, 6, 0, 1, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), 0, SysTime(DateTime(1999, 7, 6, 0, 0, 33), d));
        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), -1, SysTime(DateTime(1999, 7, 6, 0, 59, 33), d));

        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), 1, SysTime(DateTime(1999, 7, 5, 23, 0, 33), d));
        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), 0, SysTime(DateTime(1999, 7, 5, 23, 59, 33), d));
        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), -1, SysTime(DateTime(1999, 7, 5, 23, 58, 33), d));

        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), 1, SysTime(DateTime(1998, 12, 31, 23, 0, 33), d));
        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), 0, SysTime(DateTime(1998, 12, 31, 23, 59, 33), d));
        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), -1, SysTime(DateTime(1998, 12, 31, 23, 58, 33), d));

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

        testST(beforeBC, 689, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));
        testST(beforeBC, 690, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
        testST(beforeBC, 691, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
        testST(beforeBC, 960, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, 1439, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d));
        testST(beforeBC, 1440, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, 1441, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d));
        testST(beforeBC, 2880, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));

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

        testST(beforeBC, -749, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
        testST(beforeBC, -750, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
        testST(beforeBC, -751, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));
        testST(beforeBC, -960, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, -1439, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d));
        testST(beforeBC, -1440, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, -1441, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d));
        testST(beforeBC, -2880, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));

        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d));

        testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 11, 0, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 11, 58, 33), d));

        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 1, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d));
        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 0, 59, 33), d));

        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), 1, SysTime(DateTime(-1999, 7, 5, 23, 0, 33), d));
        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), 0, SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d));
        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), -1, SysTime(DateTime(-1999, 7, 5, 23, 58, 33), d));

        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), 1, SysTime(DateTime(-2000, 12, 31, 23, 0, 33), d));
        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), 0, SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d));
        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), -1, SysTime(DateTime(-2000, 12, 31, 23, 58, 33), d));

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

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

        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 1_052_760, SysTime(DateTime(-1, 1, 1, 11, 30, 33), d));
        testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -1_052_760, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));

        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 1_052_782, SysTime(DateTime(-1, 1, 1, 11, 52, 33), d));
        testST(SysTime(DateTime(1, 1, 1, 13, 52, 33), d), -1_052_782, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.roll!"minutes"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 0)));
            sysTime.roll!"minutes"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999));
            sysTime.roll!"minutes"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999)));
            sysTime.roll!"minutes"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 0));
            sysTime.roll!"minutes"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 0)));
            sysTime.roll!"minutes"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"minutes"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 59), hnsecs(9_999_999)));
            sysTime.roll!"minutes"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"minutes"(1).roll!"minutes"(-79);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 41, 59), hnsecs(9_999_999)));
        }

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

    //Test roll!"seconds"().
    unittest
    {
        static void testST(SysTime orig, int seconds, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            orig.roll!"seconds"(seconds);
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        immutable d = msecs(274);
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d);
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 35), d));
        testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 12, 30, 36), d));
        testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 12, 30, 37), d));
        testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 12, 30, 38), d));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 43), d));
        testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 12, 30, 48), d));
        testST(beforeAD, 26, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));
        testST(beforeAD, 27, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
        testST(beforeAD, 30, SysTime(DateTime(1999, 7, 6, 12, 30, 3), d));
        testST(beforeAD, 59, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d));
        testST(beforeAD, 60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 61, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));

        testST(beforeAD, 1766, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));
        testST(beforeAD, 1767, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
        testST(beforeAD, 1768, SysTime(DateTime(1999, 7, 6, 12, 30, 1), d));
        testST(beforeAD, 2007, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
        testST(beforeAD, 3599, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d));
        testST(beforeAD, 3600, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));
        testST(beforeAD, 3601, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d));
        testST(beforeAD, 7200, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d));

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

        testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), 1, SysTime(DateTime(1999, 7, 6, 12, 30, 1), d));
        testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), 0, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d));
        testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), -1, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d));

        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), 1, SysTime(DateTime(1999, 7, 6, 12, 0, 1), d));
        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), 0, SysTime(DateTime(1999, 7, 6, 12, 0, 0), d));
        testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), -1, SysTime(DateTime(1999, 7, 6, 12, 0, 59), d));

        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), 1, SysTime(DateTime(1999, 7, 6, 0, 0, 1), d));
        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), 0, SysTime(DateTime(1999, 7, 6, 0, 0, 0), d));
        testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), -1, SysTime(DateTime(1999, 7, 6, 0, 0, 59), d));

        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), 1, SysTime(DateTime(1999, 7, 5, 23, 59, 0), d));
        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), 0, SysTime(DateTime(1999, 7, 5, 23, 59, 59), d));
        testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), -1, SysTime(DateTime(1999, 7, 5, 23, 59, 58), d));

        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(1998, 12, 31, 23, 59, 0), d));
        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), 0, SysTime(DateTime(1998, 12, 31, 23, 59, 59), d));
        testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), -1, SysTime(DateTime(1998, 12, 31, 23, 59, 58), d));

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

        testST(beforeBC, 1766, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d));
        testST(beforeBC, 1767, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
        testST(beforeBC, 1768, SysTime(DateTime(-1999, 7, 6, 12, 30, 1), d));
        testST(beforeBC, 2007, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
        testST(beforeBC, 3599, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d));
        testST(beforeBC, 3600, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));
        testST(beforeBC, 3601, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d));
        testST(beforeBC, 7200, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d));

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

        testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 1), d));
        testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d));
        testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d));

        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 0, 1), d));
        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d));
        testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 0, 59), d));

        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 0, 1), d));
        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d));
        testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 0, 0, 59), d));

        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), 1, SysTime(DateTime(-1999, 7, 5, 23, 59, 0), d));
        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), 0, SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d));
        testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), -1, SysTime(DateTime(-1999, 7, 5, 23, 59, 58), d));

        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(-2000, 12, 31, 23, 59, 0), d));
        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), 0, SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d));
        testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), -1, SysTime(DateTime(-2000, 12, 31, 23, 59, 58), d));

        //Test Both
        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0), d), -1, SysTime(DateTime(1, 1, 1, 0, 0, 59), d));
        testST(SysTime(DateTime(0, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(0, 12, 31, 23, 59, 0), d));

        testST(SysTime(DateTime(0, 1, 1, 0, 0, 0), d), -1, SysTime(DateTime(0, 1, 1, 0, 0, 59), d));
        testST(SysTime(DateTime(-1, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(-1, 12, 31, 23, 59, 0), d));

        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 63_165_600L, SysTime(DateTime(-1, 1, 1, 11, 30, 33), d));
        testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -63_165_600L, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));

        testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 63_165_617L, SysTime(DateTime(-1, 1, 1, 11, 30, 50), d));
        testST(SysTime(DateTime(1, 1, 1, 13, 30, 50), d), -63_165_617L, SysTime(DateTime(1, 1, 1, 13, 30, 33), d));

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0));
            sysTime.roll!"seconds"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59)));
            sysTime.roll!"seconds"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        }

        {
            auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999));
            sysTime.roll!"seconds"(-1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999)));
            sysTime.roll!"seconds"(1);
            assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59));
            sysTime.roll!"seconds"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0)));
            sysTime.roll!"seconds"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"seconds"(1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0), hnsecs(9_999_999)));
            sysTime.roll!"seconds"(-1);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        }

        {
            auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            sysTime.roll!"seconds"(1).roll!"seconds"(-102);
            assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 18), hnsecs(9_999_999)));
        }

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


    //Shares documentation with "days" version.
    ref SysTime roll(string units)(long value) @safe nothrow
        if(units == "msecs" ||
           units == "usecs" ||
           units == "hnsecs")
    {
        auto hnsecs = adjTime;
        immutable days = splitUnitsFromHNSecs!"days"(hnsecs);
        immutable negative = hnsecs < 0;

        if(negative)
            hnsecs += convert!("hours", "hnsecs")(24);

        immutable seconds = splitUnitsFromHNSecs!"seconds"(hnsecs);
        hnsecs += convert!(units, "hnsecs")(value);
        hnsecs %= convert!("seconds", "hnsecs")(1);

        if(hnsecs < 0)
            hnsecs += convert!("seconds", "hnsecs")(1);
        hnsecs += convert!("seconds", "hnsecs")(seconds);

        if(negative)
            hnsecs -= convert!("hours", "hnsecs")(24);

        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);
        adjTime = newDaysHNSecs + hnsecs;
        return this;
    }


    //Test roll!"msecs"().
    unittest
    {
        static void testST(SysTime orig, int milliseconds, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            orig.roll!"msecs"(milliseconds);
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274));
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(275)));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(276)));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(284)));
        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(374)));
        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(275)));
        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(1)));
        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));

        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(273)));
        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(272)));
        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(264)));
        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(174)));
        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(273)));
        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999)));

        //Test B.C.
        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274));
        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(275)));
        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(276)));
        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(284)));
        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(374)));
        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(275)));
        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(1)));
        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));

        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(273)));
        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(272)));
        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(264)));
        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(174)));
        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(273)));
        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)));
        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));
        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999)));

        //Test Both
        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(1)));
        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -1, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(999)));
        testST(beforeBoth1, -2, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(998)));
        testST(beforeBoth1, -1000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -2000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(445)));

        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_989_999)));
        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9999)));
        testST(beforeBoth2, 2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19_999)));
        testST(beforeBoth2, 1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(5_549_999)));

        {
            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            st.roll!"msecs"(1202).roll!"msecs"(-703);
            assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(4_989_999)));
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.addMSecs(4)));
        //static assert(!__traits(compiles, ist.addMSecs(4)));
    }

    //Test roll!"usecs"().
    unittest
    {
        static void testST(SysTime orig, long microseconds, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            orig.roll!"usecs"(microseconds);
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274));
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(275)));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(276)));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(284)));
        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(374)));
        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999)));
        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1000)));
        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1274)));
        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1275)));
        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(2274)));
        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(26_999)));
        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(27_000)));
        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(27_001)));
        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(766_999)));
        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(767_000)));
        testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));

        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(273)));
        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(272)));
        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(264)));
        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(174)));
        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_999)));
        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_274)));
        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_273)));
        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(998_274)));
        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(967_000)));
        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(966_999)));
        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(167_000)));
        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(166_999)));
        testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)));

        //Test B.C.
        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274));
        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(275)));
        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(276)));
        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(284)));
        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(374)));
        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999)));
        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1000)));
        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1274)));
        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1275)));
        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(2274)));
        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(26_999)));
        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(27_000)));
        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(27_001)));
        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(766_999)));
        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(767_000)));
        testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));

        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(273)));
        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(272)));
        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(264)));
        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(174)));
        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_999)));
        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_274)));
        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_273)));
        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(998_274)));
        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(967_000)));
        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(966_999)));
        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(167_000)));
        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(166_999)));
        testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));
        testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)));

        //Test Both
        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(1)));
        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -1, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_999)));
        testST(beforeBoth1, -2, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_998)));
        testST(beforeBoth1, -1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_000)));
        testST(beforeBoth1, -2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(998_000)));
        testST(beforeBoth1, -2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(997_445)));
        testST(beforeBoth1, -1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(666_667)));

        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_989)));
        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9)));
        testST(beforeBoth2, 2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19)));
        testST(beforeBoth2, 1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9999)));
        testST(beforeBoth2, 2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19_999)));
        testST(beforeBoth2, 2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(25_549)));
        testST(beforeBoth2, 1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(3_333_329)));

        {
            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            st.roll!"usecs"(9_020_027);
            assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(200_269)));
        }

        {
            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            st.roll!"usecs"(9_020_027).roll!"usecs"(-70_034);
            assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_499_929)));
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.roll!"usecs"(4)));
        //static assert(!__traits(compiles, ist.roll!"usecs"(4)));
    }

    //Test roll!"hnsecs"().
    unittest
    {
        static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            orig.roll!"hnsecs"(hnsecs);
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        auto dtAD = DateTime(1999, 7, 6, 12, 30, 33);
        auto beforeAD = SysTime(dtAD, hnsecs(274));
        testST(beforeAD, 0, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, 1, SysTime(dtAD, hnsecs(275)));
        testST(beforeAD, 2, SysTime(dtAD, hnsecs(276)));
        testST(beforeAD, 10, SysTime(dtAD, hnsecs(284)));
        testST(beforeAD, 100, SysTime(dtAD, hnsecs(374)));
        testST(beforeAD, 725, SysTime(dtAD, hnsecs(999)));
        testST(beforeAD, 726, SysTime(dtAD, hnsecs(1000)));
        testST(beforeAD, 1000, SysTime(dtAD, hnsecs(1274)));
        testST(beforeAD, 1001, SysTime(dtAD, hnsecs(1275)));
        testST(beforeAD, 2000, SysTime(dtAD, hnsecs(2274)));
        testST(beforeAD, 26_725, SysTime(dtAD, hnsecs(26_999)));
        testST(beforeAD, 26_726, SysTime(dtAD, hnsecs(27_000)));
        testST(beforeAD, 26_727, SysTime(dtAD, hnsecs(27_001)));
        testST(beforeAD, 1_766_725, SysTime(dtAD, hnsecs(1_766_999)));
        testST(beforeAD, 1_766_726, SysTime(dtAD, hnsecs(1_767_000)));
        testST(beforeAD, 1_000_000, SysTime(dtAD, hnsecs(1_000_274)));
        testST(beforeAD, 60_000_000L, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, 3_600_000_000L, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, 600_000_000L, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, 36_000_000_000L, SysTime(dtAD, hnsecs(274)));

        testST(beforeAD, -1, SysTime(dtAD, hnsecs(273)));
        testST(beforeAD, -2, SysTime(dtAD, hnsecs(272)));
        testST(beforeAD, -10, SysTime(dtAD, hnsecs(264)));
        testST(beforeAD, -100, SysTime(dtAD, hnsecs(174)));
        testST(beforeAD, -274, SysTime(dtAD));
        testST(beforeAD, -275, SysTime(dtAD, hnsecs(9_999_999)));
        testST(beforeAD, -1000, SysTime(dtAD, hnsecs(9_999_274)));
        testST(beforeAD, -1001, SysTime(dtAD, hnsecs(9_999_273)));
        testST(beforeAD, -2000, SysTime(dtAD, hnsecs(9_998_274)));
        testST(beforeAD, -33_274, SysTime(dtAD, hnsecs(9_967_000)));
        testST(beforeAD, -33_275, SysTime(dtAD, hnsecs(9_966_999)));
        testST(beforeAD, -1_833_274, SysTime(dtAD, hnsecs(8_167_000)));
        testST(beforeAD, -1_833_275, SysTime(dtAD, hnsecs(8_166_999)));
        testST(beforeAD, -1_000_000, SysTime(dtAD, hnsecs(9_000_274)));
        testST(beforeAD, -60_000_000L, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, -3_600_000_000L, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, -600_000_000L, SysTime(dtAD, hnsecs(274)));
        testST(beforeAD, -36_000_000_000L, SysTime(dtAD, hnsecs(274)));

        //Test B.C.
        auto dtBC = DateTime(-1999, 7, 6, 12, 30, 33);
        auto beforeBC = SysTime(dtBC, hnsecs(274));
        testST(beforeBC, 0, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, 1, SysTime(dtBC, hnsecs(275)));
        testST(beforeBC, 2, SysTime(dtBC, hnsecs(276)));
        testST(beforeBC, 10, SysTime(dtBC, hnsecs(284)));
        testST(beforeBC, 100, SysTime(dtBC, hnsecs(374)));
        testST(beforeBC, 725, SysTime(dtBC, hnsecs(999)));
        testST(beforeBC, 726, SysTime(dtBC, hnsecs(1000)));
        testST(beforeBC, 1000, SysTime(dtBC, hnsecs(1274)));
        testST(beforeBC, 1001, SysTime(dtBC, hnsecs(1275)));
        testST(beforeBC, 2000, SysTime(dtBC, hnsecs(2274)));
        testST(beforeBC, 26_725, SysTime(dtBC, hnsecs(26_999)));
        testST(beforeBC, 26_726, SysTime(dtBC, hnsecs(27_000)));
        testST(beforeBC, 26_727, SysTime(dtBC, hnsecs(27_001)));
        testST(beforeBC, 1_766_725, SysTime(dtBC, hnsecs(1_766_999)));
        testST(beforeBC, 1_766_726, SysTime(dtBC, hnsecs(1_767_000)));
        testST(beforeBC, 1_000_000, SysTime(dtBC, hnsecs(1_000_274)));
        testST(beforeBC, 60_000_000L, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, 3_600_000_000L, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, 600_000_000L, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, 36_000_000_000L, SysTime(dtBC, hnsecs(274)));

        testST(beforeBC, -1, SysTime(dtBC, hnsecs(273)));
        testST(beforeBC, -2, SysTime(dtBC, hnsecs(272)));
        testST(beforeBC, -10, SysTime(dtBC, hnsecs(264)));
        testST(beforeBC, -100, SysTime(dtBC, hnsecs(174)));
        testST(beforeBC, -274, SysTime(dtBC));
        testST(beforeBC, -275, SysTime(dtBC, hnsecs(9_999_999)));
        testST(beforeBC, -1000, SysTime(dtBC, hnsecs(9_999_274)));
        testST(beforeBC, -1001, SysTime(dtBC, hnsecs(9_999_273)));
        testST(beforeBC, -2000, SysTime(dtBC, hnsecs(9_998_274)));
        testST(beforeBC, -33_274, SysTime(dtBC, hnsecs(9_967_000)));
        testST(beforeBC, -33_275, SysTime(dtBC, hnsecs(9_966_999)));
        testST(beforeBC, -1_833_274, SysTime(dtBC, hnsecs(8_167_000)));
        testST(beforeBC, -1_833_275, SysTime(dtBC, hnsecs(8_166_999)));
        testST(beforeBC, -1_000_000, SysTime(dtBC, hnsecs(9_000_274)));
        testST(beforeBC, -60_000_000L, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, -3_600_000_000L, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, -600_000_000L, SysTime(dtBC, hnsecs(274)));
        testST(beforeBC, -36_000_000_000L, SysTime(dtBC, hnsecs(274)));

        //Test Both
        auto dtBoth1 = DateTime(1, 1, 1, 0, 0, 0);
        auto beforeBoth1 = SysTime(dtBoth1);
        testST(beforeBoth1, 1, SysTime(dtBoth1, hnsecs(1)));
        testST(beforeBoth1, 0, SysTime(dtBoth1));
        testST(beforeBoth1, -1, SysTime(dtBoth1, hnsecs(9_999_999)));
        testST(beforeBoth1, -2, SysTime(dtBoth1, hnsecs(9_999_998)));
        testST(beforeBoth1, -1000, SysTime(dtBoth1, hnsecs(9_999_000)));
        testST(beforeBoth1, -2000, SysTime(dtBoth1, hnsecs(9_998_000)));
        testST(beforeBoth1, -2555, SysTime(dtBoth1, hnsecs(9_997_445)));
        testST(beforeBoth1, -1_000_000, SysTime(dtBoth1, hnsecs(9_000_000)));
        testST(beforeBoth1, -2_000_000, SysTime(dtBoth1, hnsecs(8_000_000)));
        testST(beforeBoth1, -2_333_333, SysTime(dtBoth1, hnsecs(7_666_667)));
        testST(beforeBoth1, -10_000_000, SysTime(dtBoth1));
        testST(beforeBoth1, -20_000_000, SysTime(dtBoth1));
        testST(beforeBoth1, -20_888_888, SysTime(dtBoth1, hnsecs(9_111_112)));

        auto dtBoth2 = DateTime(0, 12, 31, 23, 59, 59);
        auto beforeBoth2 = SysTime(dtBoth2, hnsecs(9_999_999));
        testST(beforeBoth2, -1, SysTime(dtBoth2, hnsecs(9_999_998)));
        testST(beforeBoth2, 0, SysTime(dtBoth2, hnsecs(9_999_999)));
        testST(beforeBoth2, 1, SysTime(dtBoth2));
        testST(beforeBoth2, 2, SysTime(dtBoth2, hnsecs(1)));
        testST(beforeBoth2, 1000, SysTime(dtBoth2, hnsecs(999)));
        testST(beforeBoth2, 2000, SysTime(dtBoth2, hnsecs(1999)));
        testST(beforeBoth2, 2555, SysTime(dtBoth2, hnsecs(2554)));
        testST(beforeBoth2, 1_000_000, SysTime(dtBoth2, hnsecs(999_999)));
        testST(beforeBoth2, 2_000_000, SysTime(dtBoth2, hnsecs(1_999_999)));
        testST(beforeBoth2, 2_333_333, SysTime(dtBoth2, hnsecs(2_333_332)));
        testST(beforeBoth2, 10_000_000, SysTime(dtBoth2, hnsecs(9_999_999)));
        testST(beforeBoth2, 20_000_000, SysTime(dtBoth2, hnsecs(9_999_999)));
        testST(beforeBoth2, 20_888_888, SysTime(dtBoth2, hnsecs(888_887)));

        {
            auto st = SysTime(dtBoth2, hnsecs(9_999_999));
            st.roll!"hnsecs"(70_777_222).roll!"hnsecs"(-222_555_292);
            assert(st == SysTime(dtBoth2, hnsecs(8_221_929)));
        }

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.roll!"hnsecs"(4)));
        //static assert(!__traits(compiles, ist.roll!"hnsecs"(4)));
    }


    /++
        Gives the result of adding or subtracting a $(CXREF time, Duration) from
        this $(LREF SysTime).

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

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

        Params:
            duration = The $(CXREF time, Duration) to add to or subtract from
                       this $(LREF SysTime).
      +/
    SysTime opBinary(string op)(Duration duration) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        SysTime retval = SysTime(this._stdTime, this._timezone);
        immutable hnsecs = duration.total!"hnsecs";
        mixin("retval._stdTime " ~ op ~ "= hnsecs;");
        return retval;
    }

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

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

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

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

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));

        assert(st + dur!"weeks"(7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33), hnsecs(2_345_678)));
        assert(st + dur!"weeks"(-7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33), hnsecs(2_345_678)));
        assert(st + dur!"days"(7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33), hnsecs(2_345_678)));
        assert(st + dur!"days"(-7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33), hnsecs(2_345_678)));
        assert(st + dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33), hnsecs(2_345_678)));
        assert(st + dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33), hnsecs(2_345_678)));
        assert(st + dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33), hnsecs(2_345_678)));
        assert(st + dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33), hnsecs(2_345_678)));
        assert(st + dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40), hnsecs(2_345_678)));
        assert(st + dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26), hnsecs(2_345_678)));
        assert(st + dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_415_678)));
        assert(st + dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_275_678)));
        assert(st + dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
        assert(st + dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
        assert(st + dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_685)));
        assert(st + dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_671)));

        assert(st - dur!"weeks"(-7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33), hnsecs(2_345_678)));
        assert(st - dur!"weeks"(7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33), hnsecs(2_345_678)));
        assert(st - dur!"days"(-7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33), hnsecs(2_345_678)));
        assert(st - dur!"days"(7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33), hnsecs(2_345_678)));
        assert(st - dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33), hnsecs(2_345_678)));
        assert(st - dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33), hnsecs(2_345_678)));
        assert(st - dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33), hnsecs(2_345_678)));
        assert(st - dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33), hnsecs(2_345_678)));
        assert(st - dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40), hnsecs(2_345_678)));
        assert(st - dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26), hnsecs(2_345_678)));
        assert(st - dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_415_678)));
        assert(st - dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_275_678)));
        assert(st - dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
        assert(st - dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
        assert(st - dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_685)));
        assert(st - dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_671)));

        static void testST(in SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;
            auto result = orig + dur!"hnsecs"(hnsecs);
            if(result != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", result, expected), __FILE__, line);
        }

        //Test A.D.
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274));
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274)));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(275)));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(276)));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(284)));
        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(374)));
        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(999)));
        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1000)));
        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1274)));
        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1275)));
        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2274)));
        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
        testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
        testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 39), hnsecs(274)));
        testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 36, 33), hnsecs(274)));
        testST(beforeAD, 600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 31, 33), hnsecs(274)));
        testST(beforeAD, 36_000_000_000L, SysTime(DateTime(1999, 7, 6, 13, 30, 33), hnsecs(274)));

        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(273)));
        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(272)));
        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(264)));
        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(174)));
        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
        testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
        testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 27), hnsecs(274)));
        testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 24, 33), hnsecs(274)));
        testST(beforeAD, -600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 29, 33), hnsecs(274)));
        testST(beforeAD, -36_000_000_000L, SysTime(DateTime(1999, 7, 6, 11, 30, 33), hnsecs(274)));

        //Test B.C.
        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274));
        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274)));
        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(275)));
        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(276)));
        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(284)));
        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(374)));
        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(999)));
        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1000)));
        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1274)));
        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1275)));
        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(2274)));
        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
        testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
        testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 39), hnsecs(274)));
        testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 36, 33), hnsecs(274)));
        testST(beforeBC, 600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), hnsecs(274)));
        testST(beforeBC, 36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), hnsecs(274)));

        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(273)));
        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(272)));
        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(264)));
        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(174)));
        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
        testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
        testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 27), hnsecs(274)));
        testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 24, 33), hnsecs(274)));
        testST(beforeBC, -600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), hnsecs(274)));
        testST(beforeBC, -36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), hnsecs(274)));

        //Test Both
        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth1, -2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
        testST(beforeBoth1, -1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_000)));
        testST(beforeBoth1, -2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_998_000)));
        testST(beforeBoth1, -2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_997_445)));
        testST(beforeBoth1, -1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_000_000)));
        testST(beforeBoth1, -2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(8_000_000)));
        testST(beforeBoth1, -2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(7_666_667)));
        testST(beforeBoth1, -10_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59)));
        testST(beforeBoth1, -20_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 58)));
        testST(beforeBoth1, -20_888_888, SysTime(DateTime(0, 12, 31, 23, 59, 57), hnsecs(9_111_112)));

        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth2, 2, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
        testST(beforeBoth2, 1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999)));
        testST(beforeBoth2, 2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1999)));
        testST(beforeBoth2, 2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2554)));
        testST(beforeBoth2, 1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999_999)));
        testST(beforeBoth2, 2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1_999_999)));
        testST(beforeBoth2, 2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2_333_332)));
        testST(beforeBoth2, 10_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        testST(beforeBoth2, 20_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 1), hnsecs(9_999_999)));
        testST(beforeBoth2, 20_888_888, SysTime(DateTime(1, 1, 1, 0, 0, 2), hnsecs(888_887)));

        auto duration = dur!"seconds"(12);
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst + duration == SysTime(DateTime(1999, 7, 6, 12, 30, 45)));
        //assert(ist + duration == SysTime(DateTime(1999, 7, 6, 12, 30, 45)));
        assert(cst - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21)));
        //assert(ist - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21)));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines + and - with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    SysTime opBinary(string op)(TickDuration td) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        SysTime retval = SysTime(this._stdTime, this._timezone);
        immutable hnsecs = td.hnsecs;
        mixin("retval._stdTime " ~ op ~ "= hnsecs;");
        return retval;
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));

            assert(st + TickDuration.from!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
            assert(st + TickDuration.from!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));

            assert(st - TickDuration.from!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
            assert(st - TickDuration.from!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
        }
    }


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

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

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

        Params:
            duration = The $(CXREF time, Duration) to add to or subtract from
                       this $(LREF SysTime).
      +/
    ref SysTime opOpAssign(string op)(Duration duration) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        immutable hnsecs = duration.total!"hnsecs";
        mixin("_stdTime " ~ op ~ "= hnsecs;");
        return this;
    }

    unittest
    {
        auto before = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(before + dur!"weeks"(7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33)));
        assert(before + dur!"weeks"(-7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33)));
        assert(before + dur!"days"(7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33)));
        assert(before + dur!"days"(-7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33)));

        assert(before + dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33)));
        assert(before + dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33)));
        assert(before + dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33)));
        assert(before + dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33)));
        assert(before + dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40)));
        assert(before + dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26)));
        assert(before + dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(7)));
        assert(before + dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), msecs(993)));
        assert(before + dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(7)));
        assert(before + dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), usecs(999_993)));
        assert(before + dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(7)));
        assert(before + dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_993)));

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

        assert(before - dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33)));
        assert(before - dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33)));
        assert(before - dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33)));
        assert(before - dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33)));
        assert(before - dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40)));
        assert(before - dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26)));
        assert(before - dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(7)));
        assert(before - dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), msecs(993)));
        assert(before - dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(7)));
        assert(before - dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), usecs(999_993)));
        assert(before - dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(7)));
        assert(before - dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_993)));

        static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;

            auto r = orig += dur!"hnsecs"(hnsecs);
            if(orig != expected)
                throw new AssertError(format("Failed 1. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
            if(r != expected)
                throw new AssertError(format("Failed 2. actual [%s] != expected [%s]", r, expected), __FILE__, line);
        }

        //Test A.D.
        auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274));
        testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274)));
        testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(275)));
        testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(276)));
        testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(284)));
        testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(374)));
        testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(999)));
        testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1000)));
        testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1274)));
        testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1275)));
        testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2274)));
        testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
        testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
        testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
        testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
        testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
        testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
        testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 39), hnsecs(274)));
        testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 36, 33), hnsecs(274)));
        testST(beforeAD, 600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 31, 33), hnsecs(274)));
        testST(beforeAD, 36_000_000_000L, SysTime(DateTime(1999, 7, 6, 13, 30, 33), hnsecs(274)));

        testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(273)));
        testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(272)));
        testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(264)));
        testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(174)));
        testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
        testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
        testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
        testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
        testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
        testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
        testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
        testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
        testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
        testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 27), hnsecs(274)));
        testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 24, 33), hnsecs(274)));
        testST(beforeAD, -600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 29, 33), hnsecs(274)));
        testST(beforeAD, -36_000_000_000L, SysTime(DateTime(1999, 7, 6, 11, 30, 33), hnsecs(274)));

        //Test B.C.
        auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274));
        testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274)));
        testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(275)));
        testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(276)));
        testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(284)));
        testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(374)));
        testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(999)));
        testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1000)));
        testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1274)));
        testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1275)));
        testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(2274)));
        testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(26_999)));
        testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_000)));
        testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_001)));
        testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_766_999)));
        testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_767_000)));
        testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_000_274)));
        testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 39), hnsecs(274)));
        testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 36, 33), hnsecs(274)));
        testST(beforeBC, 600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), hnsecs(274)));
        testST(beforeBC, 36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), hnsecs(274)));

        testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(273)));
        testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(272)));
        testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(264)));
        testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(174)));
        testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_999)));
        testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_274)));
        testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_273)));
        testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_998_274)));
        testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_967_000)));
        testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_966_999)));
        testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_167_000)));
        testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_166_999)));
        testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_000_274)));
        testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 27), hnsecs(274)));
        testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 24, 33), hnsecs(274)));
        testST(beforeBC, -600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), hnsecs(274)));
        testST(beforeBC, -36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), hnsecs(274)));

        //Test Both
        auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0));
        testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
        testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth1, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth1, -2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
        testST(beforeBoth1, -1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_000)));
        testST(beforeBoth1, -2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_998_000)));
        testST(beforeBoth1, -2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_997_445)));
        testST(beforeBoth1, -1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_000_000)));
        testST(beforeBoth1, -2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(8_000_000)));
        testST(beforeBoth1, -2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(7_666_667)));
        testST(beforeBoth1, -10_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59)));
        testST(beforeBoth1, -20_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 58)));
        testST(beforeBoth1, -20_888_888, SysTime(DateTime(0, 12, 31, 23, 59, 57), hnsecs(9_111_112)));

        auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
        testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)));
        testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(beforeBoth2, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(beforeBoth2, 2, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
        testST(beforeBoth2, 1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999)));
        testST(beforeBoth2, 2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1999)));
        testST(beforeBoth2, 2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2554)));
        testST(beforeBoth2, 1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999_999)));
        testST(beforeBoth2, 2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1_999_999)));
        testST(beforeBoth2, 2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2_333_332)));
        testST(beforeBoth2, 10_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        testST(beforeBoth2, 20_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 1), hnsecs(9_999_999)));
        testST(beforeBoth2, 20_888_888, SysTime(DateTime(1, 1, 1, 0, 0, 2), hnsecs(888_887)));

        {
            auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999));
            (st += dur!"hnsecs"(52)) += dur!"seconds"(-907);
            assert(st == SysTime(DateTime(0, 12, 31, 23, 44, 53), hnsecs(51)));
        }

        auto duration = dur!"seconds"(12);
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst += duration));
        //static assert(!__traits(compiles, ist += duration));
        static assert(!__traits(compiles, cst -= duration));
        //static assert(!__traits(compiles, ist -= duration));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines += and -= with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    ref SysTime opOpAssign(string op)(TickDuration td) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        immutable hnsecs = td.hnsecs;
        mixin("_stdTime " ~ op ~ "= hnsecs;");
        return this;
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            {
                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
                st += TickDuration.from!"usecs"(7);
                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
            }
            {
                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
                st += TickDuration.from!"usecs"(-7);
                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
            }

            {
                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
                st -= TickDuration.from!"usecs"(-7);
                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748)));
            }
            {
                auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678));
                st -= TickDuration.from!"usecs"(7);
                assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608)));
            }
        }
    }


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

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

        $(BOOKTABLE,
        $(TR $(TD SysTime) $(TD -) $(TD SysTime) $(TD -->) $(TD duration))
        )
      +/
    Duration opBinary(string op)(in SysTime rhs) @safe const pure nothrow
        if(op == "-")
    {
        return dur!"hnsecs"(_stdTime - rhs._stdTime);
    }

    unittest
    {
        assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1998, 7, 6, 12, 30, 33)) ==
                    dur!"seconds"(31_536_000));
        assert(SysTime(DateTime(1998, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) ==
                    dur!"seconds"(-31_536_000));

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

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

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

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

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

        {
            auto dt = DateTime(1999, 7, 6, 12, 30, 33);
            assert(SysTime(dt, msecs(532)) - SysTime(dt) == msecs(532));
            assert(SysTime(dt) - SysTime(dt, msecs(532)) == msecs(-532));

            assert(SysTime(dt, usecs(333_347)) - SysTime(dt) == usecs(333_347));
            assert(SysTime(dt) - SysTime(dt, usecs(333_347)) == usecs(-333_347));

            assert(SysTime(dt, hnsecs(1_234_567)) - SysTime(dt) == hnsecs(1_234_567));
            assert(SysTime(dt) - SysTime(dt, hnsecs(1_234_567)) == hnsecs(-1_234_567));
        }

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

        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)) ==
                        dur!"hnsecs"(1));
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) ==
                        dur!"hnsecs"(-1));

        auto tz = TimeZone.getTimeZone("America/Los_Angeles");

        {
            auto dt = DateTime(2011, 1, 13, 8, 17, 2);
            auto d = msecs(296);
            assert(SysTime(dt, d, tz) - SysTime(dt, d, tz) == Duration.zero);
            assert(SysTime(dt, d, tz) - SysTime(dt, d, UTC()) == hours(8));
            assert(SysTime(dt, d, UTC()) - SysTime(dt, d, tz) == hours(-8));
        }

        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(st - st == Duration.zero);
        assert(cst - st == Duration.zero);
        //assert(ist - st == Duration.zero);

        assert(st - cst == Duration.zero);
        assert(cst - cst == Duration.zero);
        //assert(ist - cst == Duration.zero);

        //assert(st - ist == Duration.zero);
        //assert(cst - ist == Duration.zero);
        //assert(ist - ist == Duration.zero);
    }


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

        To get the difference in years, subtract the year property
        of two $(LREF SysTime)s. To get the difference in days or weeks,
        subtract the $(LREF SysTime)s themselves and use the $(CXREF time, Duration)
        that results. Because converting between months and smaller
        units requires a specific date (which $(CXREF time, Duration)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 SysTime) to subtract from this one.
      +/
    int diffMonths(in SysTime rhs) @safe const nothrow
    {
        return (cast(Date)this).diffMonths(cast(Date)rhs);
    }

    ///
    unittest
    {
        assert(SysTime(Date(1999, 2, 1)).diffMonths(
                    SysTime(Date(1999, 1, 31))) == 1);

        assert(SysTime(Date(1999, 1, 31)).diffMonths(
                    SysTime(Date(1999, 2, 1))) == -1);

        assert(SysTime(Date(1999, 3, 1)).diffMonths(
                    SysTime(Date(1999, 1, 1))) == 2);

        assert(SysTime(Date(1999, 1, 1)).diffMonths(
                    SysTime(Date(1999, 3, 31))) == -2);
    }

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(st.diffMonths(st) == 0);
        assert(cst.diffMonths(st) == 0);
        //assert(ist.diffMonths(st) == 0);

        assert(st.diffMonths(cst) == 0);
        assert(cst.diffMonths(cst) == 0);
        //assert(ist.diffMonths(cst) == 0);

        //assert(st.diffMonths(ist) == 0);
        //assert(cst.diffMonths(ist) == 0);
        //assert(ist.diffMonths(ist) == 0);
    }


    /++
        Whether this $(LREF SysTime) is in a leap year.
     +/
    @property bool isLeapYear() @safe const nothrow
    {
        return (cast(Date)this).isLeapYear;
    }

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(!st.isLeapYear);
        assert(!cst.isLeapYear);
        //assert(!ist.isLeapYear);
    }


    /++
        Day of the week this $(LREF SysTime) is on.
      +/
    @property DayOfWeek dayOfWeek() @safe const nothrow
    {
        return getDayOfWeek(dayOfGregorianCal);
    }

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(st.dayOfWeek == DayOfWeek.tue);
        assert(cst.dayOfWeek == DayOfWeek.tue);
        //assert(ist.dayOfWeek == DayOfWeek.tue);
    }


    /++
        Day of the year this $(LREF SysTime) is on.
      +/
    @property ushort dayOfYear() @safe const nothrow
    {
        return (cast(Date)this).dayOfYear;
    }

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

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(st.dayOfYear == 187);
        assert(cst.dayOfYear == 187);
        //assert(ist.dayOfYear == 187);
    }


    /++
        Day of the year.

        Params:
            day = The day of the year to set which day of the year this
                  $(LREF SysTime) is on.
      +/
    @property void dayOfYear(int day) @safe
    {
        immutable hnsecs = adjTime;
        immutable days = convert!("hnsecs", "days")(hnsecs);
        immutable theRest = hnsecs - convert!("days", "hnsecs")(days);

        auto date = Date(cast(int)days);
        date.dayOfYear = day;

        immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1);

        adjTime = newDaysHNSecs + theRest;
    }

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        st.dayOfYear = 12;
        assert(st.dayOfYear == 12);
        static assert(!__traits(compiles, cst.dayOfYear = 12));
        //static assert(!__traits(compiles, ist.dayOfYear = 12));
    }


    /++
        The Xth day of the Gregorian Calendar that this $(LREF SysTime) is on.
     +/
    @property int dayOfGregorianCal() @safe const nothrow
    {
        immutable adjustedTime = adjTime;

        //We have to add one because 0 would be midnight, January 1st, 1 A.D.,
        //which would be the 1st day of the Gregorian Calendar, not the 0th. So,
        //simply casting to days is one day off.
        if(adjustedTime > 0)
            return cast(int)getUnitsFromHNSecs!"days"(adjustedTime) + 1;

        long hnsecs = adjustedTime;
        immutable days = cast(int)splitUnitsFromHNSecs!"days"(hnsecs);

        return hnsecs == 0 ? days + 1 : days;
    }

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

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

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

    unittest
    {
        //Test A.D.
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1);
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)).dayOfGregorianCal == 1);
        assert(SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == 1);

        assert(SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1);
        assert(SysTime(DateTime(1, 1, 2, 12, 2, 9), msecs(212)).dayOfGregorianCal == 2);
        assert(SysTime(DateTime(1, 2, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 32);
        assert(SysTime(DateTime(2, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 366);
        assert(SysTime(DateTime(3, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 731);
        assert(SysTime(DateTime(4, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1096);
        assert(SysTime(DateTime(5, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1462);
        assert(SysTime(DateTime(50, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 17_898);
        assert(SysTime(DateTime(97, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 35_065);
        assert(SysTime(DateTime(100, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 36_160);
        assert(SysTime(DateTime(101, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 36_525);
        assert(SysTime(DateTime(105, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 37_986);
        assert(SysTime(DateTime(200, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 72_684);
        assert(SysTime(DateTime(201, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 73_049);
        assert(SysTime(DateTime(300, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 109_208);
        assert(SysTime(DateTime(301, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 109_573);
        assert(SysTime(DateTime(400, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 145_732);
        assert(SysTime(DateTime(401, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 146_098);
        assert(SysTime(DateTime(500, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 182_257);
        assert(SysTime(DateTime(501, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 182_622);
        assert(SysTime(DateTime(1000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 364_878);
        assert(SysTime(DateTime(1001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 365_243);
        assert(SysTime(DateTime(1600, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 584_023);
        assert(SysTime(DateTime(1601, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 584_389);
        assert(SysTime(DateTime(1900, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 693_596);
        assert(SysTime(DateTime(1901, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 693_961);
        assert(SysTime(DateTime(1945, 11, 12, 12, 2, 9), msecs(212)).dayOfGregorianCal == 710_347);
        assert(SysTime(DateTime(1999, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 729_755);
        assert(SysTime(DateTime(2000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 730_120);
        assert(SysTime(DateTime(2001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 730_486);

        assert(SysTime(DateTime(2010, 1, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_773);
        assert(SysTime(DateTime(2010, 1, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_803);
        assert(SysTime(DateTime(2010, 2, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_804);
        assert(SysTime(DateTime(2010, 2, 28, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_831);
        assert(SysTime(DateTime(2010, 3, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_832);
        assert(SysTime(DateTime(2010, 3, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_862);
        assert(SysTime(DateTime(2010, 4, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_863);
        assert(SysTime(DateTime(2010, 4, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_892);
        assert(SysTime(DateTime(2010, 5, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_893);
        assert(SysTime(DateTime(2010, 5, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_923);
        assert(SysTime(DateTime(2010, 6, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_924);
        assert(SysTime(DateTime(2010, 6, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_953);
        assert(SysTime(DateTime(2010, 7, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_954);
        assert(SysTime(DateTime(2010, 7, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_984);
        assert(SysTime(DateTime(2010, 8, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_985);
        assert(SysTime(DateTime(2010, 8, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_015);
        assert(SysTime(DateTime(2010, 9, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_016);
        assert(SysTime(DateTime(2010, 9, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_045);
        assert(SysTime(DateTime(2010, 10, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_046);
        assert(SysTime(DateTime(2010, 10, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_076);
        assert(SysTime(DateTime(2010, 11, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_077);
        assert(SysTime(DateTime(2010, 11, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_106);
        assert(SysTime(DateTime(2010, 12, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_107);
        assert(SysTime(DateTime(2010, 12, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_137);

        assert(SysTime(DateTime(2012, 2, 1, 0, 0, 0)).dayOfGregorianCal == 734_534);
        assert(SysTime(DateTime(2012, 2, 28, 0, 0, 0)).dayOfGregorianCal == 734_561);
        assert(SysTime(DateTime(2012, 2, 29, 0, 0, 0)).dayOfGregorianCal == 734_562);
        assert(SysTime(DateTime(2012, 3, 1, 0, 0, 0)).dayOfGregorianCal == 734_563);

        //Test B.C.
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == 0);
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)).dayOfGregorianCal == 0);
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59)).dayOfGregorianCal == 0);
        assert(SysTime(DateTime(0, 12, 31, 0, 0, 0), hnsecs(1)).dayOfGregorianCal == 0);
        assert(SysTime(DateTime(0, 12, 31, 0, 0, 0)).dayOfGregorianCal == 0);

        assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == -366);
        assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59), hnsecs(9_999_998)).dayOfGregorianCal == -366);
        assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59)).dayOfGregorianCal == -366);
        assert(SysTime(DateTime(-1, 12, 31, 0, 0, 0)).dayOfGregorianCal == -366);

        assert(SysTime(DateTime(0, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == 0);
        assert(SysTime(DateTime(0, 12, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1);
        assert(SysTime(DateTime(0, 12, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -30);
        assert(SysTime(DateTime(0, 11, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -31);

        assert(SysTime(DateTime(-1, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -366);
        assert(SysTime(DateTime(-1, 12, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -367);
        assert(SysTime(DateTime(-1, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730);
        assert(SysTime(DateTime(-2, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -731);
        assert(SysTime(DateTime(-2, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1095);
        assert(SysTime(DateTime(-3, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1096);
        assert(SysTime(DateTime(-3, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1460);
        assert(SysTime(DateTime(-4, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1461);
        assert(SysTime(DateTime(-4, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1826);
        assert(SysTime(DateTime(-5, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1827);
        assert(SysTime(DateTime(-5, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -2191);
        assert(SysTime(DateTime(-9, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -3652);

        assert(SysTime(DateTime(-49, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -18_262);
        assert(SysTime(DateTime(-50, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -18_627);
        assert(SysTime(DateTime(-97, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -35_794);
        assert(SysTime(DateTime(-99, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_160);
        assert(SysTime(DateTime(-99, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_524);
        assert(SysTime(DateTime(-100, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_889);
        assert(SysTime(DateTime(-101, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -37_254);
        assert(SysTime(DateTime(-105, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -38_715);
        assert(SysTime(DateTime(-200, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -73_413);
        assert(SysTime(DateTime(-201, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -73_778);
        assert(SysTime(DateTime(-300, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -109_937);
        assert(SysTime(DateTime(-301, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -110_302);
        assert(SysTime(DateTime(-400, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_097);
        assert(SysTime(DateTime(-400, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_462);
        assert(SysTime(DateTime(-401, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_827);
        assert(SysTime(DateTime(-499, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -182_621);
        assert(SysTime(DateTime(-500, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -182_986);
        assert(SysTime(DateTime(-501, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -183_351);
        assert(SysTime(DateTime(-1000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -365_607);
        assert(SysTime(DateTime(-1001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -365_972);
        assert(SysTime(DateTime(-1599, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_387);
        assert(SysTime(DateTime(-1600, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_388);
        assert(SysTime(DateTime(-1600, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_753);
        assert(SysTime(DateTime(-1601, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -585_118);
        assert(SysTime(DateTime(-1900, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -694_325);
        assert(SysTime(DateTime(-1901, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -694_690);
        assert(SysTime(DateTime(-1999, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_484);
        assert(SysTime(DateTime(-2000, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_485);
        assert(SysTime(DateTime(-2000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_850);
        assert(SysTime(DateTime(-2001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -731_215);

        assert(SysTime(DateTime(-2010, 1, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_502);
        assert(SysTime(DateTime(-2010, 1, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_472);
        assert(SysTime(DateTime(-2010, 2, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_471);
        assert(SysTime(DateTime(-2010, 2, 28, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_444);
        assert(SysTime(DateTime(-2010, 3, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_443);
        assert(SysTime(DateTime(-2010, 3, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_413);
        assert(SysTime(DateTime(-2010, 4, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_412);
        assert(SysTime(DateTime(-2010, 4, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_383);
        assert(SysTime(DateTime(-2010, 5, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_382);
        assert(SysTime(DateTime(-2010, 5, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_352);
        assert(SysTime(DateTime(-2010, 6, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_351);
        assert(SysTime(DateTime(-2010, 6, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_322);
        assert(SysTime(DateTime(-2010, 7, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_321);
        assert(SysTime(DateTime(-2010, 7, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_291);
        assert(SysTime(DateTime(-2010, 8, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_290);
        assert(SysTime(DateTime(-2010, 8, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_260);
        assert(SysTime(DateTime(-2010, 9, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_259);
        assert(SysTime(DateTime(-2010, 9, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_230);
        assert(SysTime(DateTime(-2010, 10, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_229);
        assert(SysTime(DateTime(-2010, 10, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_199);
        assert(SysTime(DateTime(-2010, 11, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_198);
        assert(SysTime(DateTime(-2010, 11, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_169);
        assert(SysTime(DateTime(-2010, 12, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_168);
        assert(SysTime(DateTime(-2010, 12, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_138);

        assert(SysTime(DateTime(-2012, 2, 1, 0, 0, 0)).dayOfGregorianCal == -735_202);
        assert(SysTime(DateTime(-2012, 2, 28, 0, 0, 0)).dayOfGregorianCal == -735_175);
        assert(SysTime(DateTime(-2012, 2, 29, 0, 0, 0)).dayOfGregorianCal == -735_174);
        assert(SysTime(DateTime(-2012, 3, 1, 0, 0, 0)).dayOfGregorianCal == -735_173);

        // Start of Hebrew Calendar
        assert(SysTime(DateTime(-3760, 9, 7, 0, 0, 0)).dayOfGregorianCal == -1_373_427);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.dayOfGregorianCal == 729_941);
        //assert(ist.dayOfGregorianCal == 729_941);
    }


    //Test that the logic for the day of the Gregorian Calendar is consistent
    //between Date and SysTime.
    unittest
    {
        void test(Date date, SysTime st, size_t line = __LINE__)
        {
            import std.format : format;

            if(date.dayOfGregorianCal != st.dayOfGregorianCal)
            {
                throw new AssertError(format("Date [%s] SysTime [%s]", date.dayOfGregorianCal, st.dayOfGregorianCal),
                                      __FILE__, line);
            }
        }

        //Test A.D.
        test(Date(1, 1, 1), SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        test(Date(1, 1, 2), SysTime(DateTime(1, 1, 2, 0, 0, 0), hnsecs(500)));
        test(Date(1, 2, 1), SysTime(DateTime(1, 2, 1, 0, 0, 0), hnsecs(50_000)));
        test(Date(2, 1, 1), SysTime(DateTime(2, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(3, 1, 1), SysTime(DateTime(3, 1, 1, 12, 13, 14)));
        test(Date(4, 1, 1), SysTime(DateTime(4, 1, 1, 12, 13, 14), hnsecs(500)));
        test(Date(5, 1, 1), SysTime(DateTime(5, 1, 1, 12, 13, 14), hnsecs(50_000)));
        test(Date(50, 1, 1), SysTime(DateTime(50, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(97, 1, 1), SysTime(DateTime(97, 1, 1, 23, 59, 59)));
        test(Date(100, 1, 1), SysTime(DateTime(100, 1, 1, 23, 59, 59), hnsecs(500)));
        test(Date(101, 1, 1), SysTime(DateTime(101, 1, 1, 23, 59, 59), hnsecs(50_000)));
        test(Date(105, 1, 1), SysTime(DateTime(105, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(200, 1, 1), SysTime(DateTime(200, 1, 1, 0, 0, 0)));
        test(Date(201, 1, 1), SysTime(DateTime(201, 1, 1, 0, 0, 0), hnsecs(500)));
        test(Date(300, 1, 1), SysTime(DateTime(300, 1, 1, 0, 0, 0), hnsecs(50_000)));
        test(Date(301, 1, 1), SysTime(DateTime(301, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(400, 1, 1), SysTime(DateTime(400, 1, 1, 12, 13, 14)));
        test(Date(401, 1, 1), SysTime(DateTime(401, 1, 1, 12, 13, 14), hnsecs(500)));
        test(Date(500, 1, 1), SysTime(DateTime(500, 1, 1, 12, 13, 14), hnsecs(50_000)));
        test(Date(501, 1, 1), SysTime(DateTime(501, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(1000, 1, 1), SysTime(DateTime(1000, 1, 1, 23, 59, 59)));
        test(Date(1001, 1, 1), SysTime(DateTime(1001, 1, 1, 23, 59, 59), hnsecs(500)));
        test(Date(1600, 1, 1), SysTime(DateTime(1600, 1, 1, 23, 59, 59), hnsecs(50_000)));
        test(Date(1601, 1, 1), SysTime(DateTime(1601, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(1900, 1, 1), SysTime(DateTime(1900, 1, 1, 0, 0, 0)));
        test(Date(1901, 1, 1), SysTime(DateTime(1901, 1, 1, 0, 0, 0), hnsecs(500)));
        test(Date(1945, 11, 12), SysTime(DateTime(1945, 11, 12, 0, 0, 0), hnsecs(50_000)));
        test(Date(1999, 1, 1), SysTime(DateTime(1999, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(1999, 7, 6), SysTime(DateTime(1999, 7, 6, 12, 13, 14)));
        test(Date(2000, 1, 1), SysTime(DateTime(2000, 1, 1, 12, 13, 14), hnsecs(500)));
        test(Date(2001, 1, 1), SysTime(DateTime(2001, 1, 1, 12, 13, 14), hnsecs(50_000)));

        test(Date(2010, 1, 1), SysTime(DateTime(2010, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(2010, 1, 31), SysTime(DateTime(2010, 1, 31, 23, 0, 0)));
        test(Date(2010, 2, 1), SysTime(DateTime(2010, 2, 1, 23, 59, 59), hnsecs(500)));
        test(Date(2010, 2, 28), SysTime(DateTime(2010, 2, 28, 23, 59, 59), hnsecs(50_000)));
        test(Date(2010, 3, 1), SysTime(DateTime(2010, 3, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(2010, 3, 31), SysTime(DateTime(2010, 3, 31, 0, 0, 0)));
        test(Date(2010, 4, 1), SysTime(DateTime(2010, 4, 1, 0, 0, 0), hnsecs(500)));
        test(Date(2010, 4, 30), SysTime(DateTime(2010, 4, 30, 0, 0, 0), hnsecs(50_000)));
        test(Date(2010, 5, 1), SysTime(DateTime(2010, 5, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(2010, 5, 31), SysTime(DateTime(2010, 5, 31, 12, 13, 14)));
        test(Date(2010, 6, 1), SysTime(DateTime(2010, 6, 1, 12, 13, 14), hnsecs(500)));
        test(Date(2010, 6, 30), SysTime(DateTime(2010, 6, 30, 12, 13, 14), hnsecs(50_000)));
        test(Date(2010, 7, 1), SysTime(DateTime(2010, 7, 1, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(2010, 7, 31), SysTime(DateTime(2010, 7, 31, 23, 59, 59)));
        test(Date(2010, 8, 1), SysTime(DateTime(2010, 8, 1, 23, 59, 59), hnsecs(500)));
        test(Date(2010, 8, 31), SysTime(DateTime(2010, 8, 31, 23, 59, 59), hnsecs(50_000)));
        test(Date(2010, 9, 1), SysTime(DateTime(2010, 9, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(2010, 9, 30), SysTime(DateTime(2010, 9, 30, 12, 0, 0)));
        test(Date(2010, 10, 1), SysTime(DateTime(2010, 10, 1, 0, 12, 0), hnsecs(500)));
        test(Date(2010, 10, 31), SysTime(DateTime(2010, 10, 31, 0, 0, 12), hnsecs(50_000)));
        test(Date(2010, 11, 1), SysTime(DateTime(2010, 11, 1, 23, 0, 0), hnsecs(9_999_999)));
        test(Date(2010, 11, 30), SysTime(DateTime(2010, 11, 30, 0, 59, 0)));
        test(Date(2010, 12, 1), SysTime(DateTime(2010, 12, 1, 0, 0, 59), hnsecs(500)));
        test(Date(2010, 12, 31), SysTime(DateTime(2010, 12, 31, 0, 59, 59), hnsecs(50_000)));

        test(Date(2012, 2, 1), SysTime(DateTime(2012, 2, 1, 23, 0, 59), hnsecs(9_999_999)));
        test(Date(2012, 2, 28), SysTime(DateTime(2012, 2, 28, 23, 59, 0)));
        test(Date(2012, 2, 29), SysTime(DateTime(2012, 2, 29, 7, 7, 7), hnsecs(7)));
        test(Date(2012, 3, 1), SysTime(DateTime(2012, 3, 1, 7, 7, 7), hnsecs(7)));

        //Test B.C.
        test(Date(0, 12, 31), SysTime(DateTime(0, 12, 31, 0, 0, 0)));
        test(Date(0, 12, 30), SysTime(DateTime(0, 12, 30, 0, 0, 0), hnsecs(500)));
        test(Date(0, 12, 1), SysTime(DateTime(0, 12, 1, 0, 0, 0), hnsecs(50_000)));
        test(Date(0, 11, 30), SysTime(DateTime(0, 11, 30, 0, 0, 0), hnsecs(9_999_999)));

        test(Date(-1, 12, 31), SysTime(DateTime(-1, 12, 31, 12, 13, 14)));
        test(Date(-1, 12, 30), SysTime(DateTime(-1, 12, 30, 12, 13, 14), hnsecs(500)));
        test(Date(-1, 1, 1), SysTime(DateTime(-1, 1, 1, 12, 13, 14), hnsecs(50_000)));
        test(Date(-2, 12, 31), SysTime(DateTime(-2, 12, 31, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(-2, 1, 1), SysTime(DateTime(-2, 1, 1, 23, 59, 59)));
        test(Date(-3, 12, 31), SysTime(DateTime(-3, 12, 31, 23, 59, 59), hnsecs(500)));
        test(Date(-3, 1, 1), SysTime(DateTime(-3, 1, 1, 23, 59, 59), hnsecs(50_000)));
        test(Date(-4, 12, 31), SysTime(DateTime(-4, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(-4, 1, 1), SysTime(DateTime(-4, 1, 1, 0, 0, 0)));
        test(Date(-5, 12, 31), SysTime(DateTime(-5, 12, 31, 0, 0, 0), hnsecs(500)));
        test(Date(-5, 1, 1), SysTime(DateTime(-5, 1, 1, 0, 0, 0), hnsecs(50_000)));
        test(Date(-9, 1, 1), SysTime(DateTime(-9, 1, 1, 0, 0, 0), hnsecs(9_999_999)));

        test(Date(-49, 1, 1), SysTime(DateTime(-49, 1, 1, 12, 13, 14)));
        test(Date(-50, 1, 1), SysTime(DateTime(-50, 1, 1, 12, 13, 14), hnsecs(500)));
        test(Date(-97, 1, 1), SysTime(DateTime(-97, 1, 1, 12, 13, 14), hnsecs(50_000)));
        test(Date(-99, 12, 31), SysTime(DateTime(-99, 12, 31, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(-99, 1, 1), SysTime(DateTime(-99, 1, 1, 23, 59, 59)));
        test(Date(-100, 1, 1), SysTime(DateTime(-100, 1, 1, 23, 59, 59), hnsecs(500)));
        test(Date(-101, 1, 1), SysTime(DateTime(-101, 1, 1, 23, 59, 59), hnsecs(50_000)));
        test(Date(-105, 1, 1), SysTime(DateTime(-105, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(-200, 1, 1), SysTime(DateTime(-200, 1, 1, 0, 0, 0)));
        test(Date(-201, 1, 1), SysTime(DateTime(-201, 1, 1, 0, 0, 0), hnsecs(500)));
        test(Date(-300, 1, 1), SysTime(DateTime(-300, 1, 1, 0, 0, 0), hnsecs(50_000)));
        test(Date(-301, 1, 1), SysTime(DateTime(-301, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(-400, 12, 31), SysTime(DateTime(-400, 12, 31, 12, 13, 14)));
        test(Date(-400, 1, 1), SysTime(DateTime(-400, 1, 1, 12, 13, 14), hnsecs(500)));
        test(Date(-401, 1, 1), SysTime(DateTime(-401, 1, 1, 12, 13, 14), hnsecs(50_000)));
        test(Date(-499, 1, 1), SysTime(DateTime(-499, 1, 1, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(-500, 1, 1), SysTime(DateTime(-500, 1, 1, 23, 59, 59)));
        test(Date(-501, 1, 1), SysTime(DateTime(-501, 1, 1, 23, 59, 59), hnsecs(500)));
        test(Date(-1000, 1, 1), SysTime(DateTime(-1000, 1, 1, 23, 59, 59), hnsecs(50_000)));
        test(Date(-1001, 1, 1), SysTime(DateTime(-1001, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(-1599, 1, 1), SysTime(DateTime(-1599, 1, 1, 0, 0, 0)));
        test(Date(-1600, 12, 31), SysTime(DateTime(-1600, 12, 31, 0, 0, 0), hnsecs(500)));
        test(Date(-1600, 1, 1), SysTime(DateTime(-1600, 1, 1, 0, 0, 0), hnsecs(50_000)));
        test(Date(-1601, 1, 1), SysTime(DateTime(-1601, 1, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(-1900, 1, 1), SysTime(DateTime(-1900, 1, 1, 12, 13, 14)));
        test(Date(-1901, 1, 1), SysTime(DateTime(-1901, 1, 1, 12, 13, 14), hnsecs(500)));
        test(Date(-1999, 1, 1), SysTime(DateTime(-1999, 1, 1, 12, 13, 14), hnsecs(50_000)));
        test(Date(-1999, 7, 6), SysTime(DateTime(-1999, 7, 6, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(-2000, 12, 31), SysTime(DateTime(-2000, 12, 31, 23, 59, 59)));
        test(Date(-2000, 1, 1), SysTime(DateTime(-2000, 1, 1, 23, 59, 59), hnsecs(500)));
        test(Date(-2001, 1, 1), SysTime(DateTime(-2001, 1, 1, 23, 59, 59), hnsecs(50_000)));

        test(Date(-2010, 1, 1), SysTime(DateTime(-2010, 1, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(-2010, 1, 31), SysTime(DateTime(-2010, 1, 31, 0, 0, 0)));
        test(Date(-2010, 2, 1), SysTime(DateTime(-2010, 2, 1, 0, 0, 0), hnsecs(500)));
        test(Date(-2010, 2, 28), SysTime(DateTime(-2010, 2, 28, 0, 0, 0), hnsecs(50_000)));
        test(Date(-2010, 3, 1), SysTime(DateTime(-2010, 3, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(-2010, 3, 31), SysTime(DateTime(-2010, 3, 31, 12, 13, 14)));
        test(Date(-2010, 4, 1), SysTime(DateTime(-2010, 4, 1, 12, 13, 14), hnsecs(500)));
        test(Date(-2010, 4, 30), SysTime(DateTime(-2010, 4, 30, 12, 13, 14), hnsecs(50_000)));
        test(Date(-2010, 5, 1), SysTime(DateTime(-2010, 5, 1, 12, 13, 14), hnsecs(9_999_999)));
        test(Date(-2010, 5, 31), SysTime(DateTime(-2010, 5, 31, 23, 59, 59)));
        test(Date(-2010, 6, 1), SysTime(DateTime(-2010, 6, 1, 23, 59, 59), hnsecs(500)));
        test(Date(-2010, 6, 30), SysTime(DateTime(-2010, 6, 30, 23, 59, 59), hnsecs(50_000)));
        test(Date(-2010, 7, 1), SysTime(DateTime(-2010, 7, 1, 23, 59, 59), hnsecs(9_999_999)));
        test(Date(-2010, 7, 31), SysTime(DateTime(-2010, 7, 31, 0, 0, 0)));
        test(Date(-2010, 8, 1), SysTime(DateTime(-2010, 8, 1, 0, 0, 0), hnsecs(500)));
        test(Date(-2010, 8, 31), SysTime(DateTime(-2010, 8, 31, 0, 0, 0), hnsecs(50_000)));
        test(Date(-2010, 9, 1), SysTime(DateTime(-2010, 9, 1, 0, 0, 0), hnsecs(9_999_999)));
        test(Date(-2010, 9, 30), SysTime(DateTime(-2010, 9, 30, 12, 0, 0)));
        test(Date(-2010, 10, 1), SysTime(DateTime(-2010, 10, 1, 0, 12, 0), hnsecs(500)));
        test(Date(-2010, 10, 31), SysTime(DateTime(-2010, 10, 31, 0, 0, 12), hnsecs(50_000)));
        test(Date(-2010, 11, 1), SysTime(DateTime(-2010, 11, 1, 23, 0, 0), hnsecs(9_999_999)));
        test(Date(-2010, 11, 30), SysTime(DateTime(-2010, 11, 30, 0, 59, 0)));
        test(Date(-2010, 12, 1), SysTime(DateTime(-2010, 12, 1, 0, 0, 59), hnsecs(500)));
        test(Date(-2010, 12, 31), SysTime(DateTime(-2010, 12, 31, 0, 59, 59), hnsecs(50_000)));

        test(Date(-2012, 2, 1), SysTime(DateTime(-2012, 2, 1, 23, 0, 59), hnsecs(9_999_999)));
        test(Date(-2012, 2, 28), SysTime(DateTime(-2012, 2, 28, 23, 59, 0)));
        test(Date(-2012, 2, 29), SysTime(DateTime(-2012, 2, 29, 7, 7, 7), hnsecs(7)));
        test(Date(-2012, 3, 1), SysTime(DateTime(-2012, 3, 1, 7, 7, 7), hnsecs(7)));

        test(Date(-3760, 9, 7), SysTime(DateTime(-3760, 9, 7, 0, 0, 0)));
    }


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

        Params:
            days = The day of the Gregorian Calendar to set this $(LREF SysTime)
                   to.
     +/
    @property void dayOfGregorianCal(int days) @safe nothrow
    {
        auto hnsecs = adjTime;
        hnsecs = removeUnitsFromHNSecs!"days"(hnsecs);

        if(hnsecs < 0)
            hnsecs += convert!("hours", "hnsecs")(24);

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

        immutable newDaysHNSecs = convert!("days", "hnsecs")(days);

        adjTime = newDaysHNSecs + hnsecs;
    }

    ///
    unittest
    {
        auto st = SysTime(DateTime(0, 1, 1, 12, 0, 0));
        st.dayOfGregorianCal = 1;
        assert(st == SysTime(DateTime(1, 1, 1, 12, 0, 0)));

        st.dayOfGregorianCal = 365;
        assert(st == SysTime(DateTime(1, 12, 31, 12, 0, 0)));

        st.dayOfGregorianCal = 366;
        assert(st == SysTime(DateTime(2, 1, 1, 12, 0, 0)));

        st.dayOfGregorianCal = 0;
        assert(st == SysTime(DateTime(0, 12, 31, 12, 0, 0)));

        st.dayOfGregorianCal = -365;
        assert(st == SysTime(DateTime(-0, 1, 1, 12, 0, 0)));

        st.dayOfGregorianCal = -366;
        assert(st == SysTime(DateTime(-1, 12, 31, 12, 0, 0)));

        st.dayOfGregorianCal = 730_120;
        assert(st == SysTime(DateTime(2000, 1, 1, 12, 0, 0)));

        st.dayOfGregorianCal = 734_137;
        assert(st == SysTime(DateTime(2010, 12, 31, 12, 0, 0)));
    }

    unittest
    {
        void testST(SysTime orig, int day, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;

            orig.dayOfGregorianCal = day;
            if(orig != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line);
        }

        //Test A.D.
        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
        testST(SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)), 1,
               SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));

        //Test B.C.
        testST(SysTime(DateTime(0, 1, 1, 0, 0, 0)), 0, SysTime(DateTime(0, 12, 31, 0, 0, 0)));
        testST(SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)), 0,
               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(1)), 0,
               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1)));
        testST(SysTime(DateTime(0, 1, 1, 23, 59, 59)), 0, SysTime(DateTime(0, 12, 31, 23, 59, 59)));

        //Test Both.
        testST(SysTime(DateTime(-512, 7, 20, 0, 0, 0)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0)));
        testST(SysTime(DateTime(-513, 6, 6, 0, 0, 0), hnsecs(1)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)));
        testST(SysTime(DateTime(-511, 5, 7, 23, 59, 59), hnsecs(9_999_999)), 1,
               SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)));

        testST(SysTime(DateTime(1607, 4, 8, 0, 0, 0)), 0, SysTime(DateTime(0, 12, 31, 0, 0, 0)));
        testST(SysTime(DateTime(1500, 3, 9, 23, 59, 59), hnsecs(9_999_999)), 0,
               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)));
        testST(SysTime(DateTime(999, 2, 10, 23, 59, 59), hnsecs(1)), 0,
               SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1)));
        testST(SysTime(DateTime(2007, 12, 11, 23, 59, 59)), 0, SysTime(DateTime(0, 12, 31, 23, 59, 59)));


        auto st = SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212));

        void testST2(int day, in SysTime expected, size_t line = __LINE__)
        {
            import std.format : format;

            st.dayOfGregorianCal = day;
            if(st != expected)
                throw new AssertError(format("Failed. actual [%s] != expected [%s]", st, expected), __FILE__, line);
        }

        //Test A.D.
        testST2(1, SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)));
        testST2(2, SysTime(DateTime(1, 1, 2, 12, 2, 9), msecs(212)));
        testST2(32, SysTime(DateTime(1, 2, 1, 12, 2, 9), msecs(212)));
        testST2(366, SysTime(DateTime(2, 1, 1, 12, 2, 9), msecs(212)));
        testST2(731, SysTime(DateTime(3, 1, 1, 12, 2, 9), msecs(212)));
        testST2(1096, SysTime(DateTime(4, 1, 1, 12, 2, 9), msecs(212)));
        testST2(1462, SysTime(DateTime(5, 1, 1, 12, 2, 9), msecs(212)));
        testST2(17_898, SysTime(DateTime(50, 1, 1, 12, 2, 9), msecs(212)));
        testST2(35_065, SysTime(DateTime(97, 1, 1, 12, 2, 9), msecs(212)));
        testST2(36_160, SysTime(DateTime(100, 1, 1, 12, 2, 9), msecs(212)));
        testST2(36_525, SysTime(DateTime(101, 1, 1, 12, 2, 9), msecs(212)));
        testST2(37_986, SysTime(DateTime(105, 1, 1, 12, 2, 9), msecs(212)));
        testST2(72_684, SysTime(DateTime(200, 1, 1, 12, 2, 9), msecs(212)));
        testST2(73_049, SysTime(DateTime(201, 1, 1, 12, 2, 9), msecs(212)));
        testST2(109_208, SysTime(DateTime(300, 1, 1, 12, 2, 9), msecs(212)));
        testST2(109_573, SysTime(DateTime(301, 1, 1, 12, 2, 9), msecs(212)));
        testST2(145_732, SysTime(DateTime(400, 1, 1, 12, 2, 9), msecs(212)));
        testST2(146_098, SysTime(DateTime(401, 1, 1, 12, 2, 9), msecs(212)));
        testST2(182_257, SysTime(DateTime(500, 1, 1, 12, 2, 9), msecs(212)));
        testST2(182_622, SysTime(DateTime(501, 1, 1, 12, 2, 9), msecs(212)));
        testST2(364_878, SysTime(DateTime(1000, 1, 1, 12, 2, 9), msecs(212)));
        testST2(365_243, SysTime(DateTime(1001, 1, 1, 12, 2, 9), msecs(212)));
        testST2(584_023, SysTime(DateTime(1600, 1, 1, 12, 2, 9), msecs(212)));
        testST2(584_389, SysTime(DateTime(1601, 1, 1, 12, 2, 9), msecs(212)));
        testST2(693_596, SysTime(DateTime(1900, 1, 1, 12, 2, 9), msecs(212)));
        testST2(693_961, SysTime(DateTime(1901, 1, 1, 12, 2, 9), msecs(212)));
        testST2(729_755, SysTime(DateTime(1999, 1, 1, 12, 2, 9), msecs(212)));
        testST2(730_120, SysTime(DateTime(2000, 1, 1, 12, 2, 9), msecs(212)));
        testST2(730_486, SysTime(DateTime(2001, 1, 1, 12, 2, 9), msecs(212)));

        testST2(733_773, SysTime(DateTime(2010, 1, 1, 12, 2, 9), msecs(212)));
        testST2(733_803, SysTime(DateTime(2010, 1, 31, 12, 2, 9), msecs(212)));
        testST2(733_804, SysTime(DateTime(2010, 2, 1, 12, 2, 9), msecs(212)));
        testST2(733_831, SysTime(DateTime(2010, 2, 28, 12, 2, 9), msecs(212)));
        testST2(733_832, SysTime(DateTime(2010, 3, 1, 12, 2, 9), msecs(212)));
        testST2(733_862, SysTime(DateTime(2010, 3, 31, 12, 2, 9), msecs(212)));
        testST2(733_863, SysTime(DateTime(2010, 4, 1, 12, 2, 9), msecs(212)));
        testST2(733_892, SysTime(DateTime(2010, 4, 30, 12, 2, 9), msecs(212)));
        testST2(733_893, SysTime(DateTime(2010, 5, 1, 12, 2, 9), msecs(212)));
        testST2(733_923, SysTime(DateTime(2010, 5, 31, 12, 2, 9), msecs(212)));
        testST2(733_924, SysTime(DateTime(2010, 6, 1, 12, 2, 9), msecs(212)));
        testST2(733_953, SysTime(DateTime(2010, 6, 30, 12, 2, 9), msecs(212)));
        testST2(733_954, SysTime(DateTime(2010, 7, 1, 12, 2, 9), msecs(212)));
        testST2(733_984, SysTime(DateTime(2010, 7, 31, 12, 2, 9), msecs(212)));
        testST2(733_985, SysTime(DateTime(2010, 8, 1, 12, 2, 9), msecs(212)));
        testST2(734_015, SysTime(DateTime(2010, 8, 31, 12, 2, 9), msecs(212)));
        testST2(734_016, SysTime(DateTime(2010, 9, 1, 12, 2, 9), msecs(212)));
        testST2(734_045, SysTime(DateTime(2010, 9, 30, 12, 2, 9), msecs(212)));
        testST2(734_046, SysTime(DateTime(2010, 10, 1, 12, 2, 9), msecs(212)));
        testST2(734_076, SysTime(DateTime(2010, 10, 31, 12, 2, 9), msecs(212)));
        testST2(734_077, SysTime(DateTime(2010, 11, 1, 12, 2, 9), msecs(212)));
        testST2(734_106, SysTime(DateTime(2010, 11, 30, 12, 2, 9), msecs(212)));
        testST2(734_107, SysTime(DateTime(2010, 12, 1, 12, 2, 9), msecs(212)));
        testST2(734_137, SysTime(DateTime(2010, 12, 31, 12, 2, 9), msecs(212)));

        testST2(734_534, SysTime(DateTime(2012, 2, 1, 12, 2, 9), msecs(212)));
        testST2(734_561, SysTime(DateTime(2012, 2, 28, 12, 2, 9), msecs(212)));
        testST2(734_562, SysTime(DateTime(2012, 2, 29, 12, 2, 9), msecs(212)));
        testST2(734_563, SysTime(DateTime(2012, 3, 1, 12, 2, 9), msecs(212)));

        testST2(734_534,  SysTime(DateTime(2012, 2, 1, 12, 2, 9), msecs(212)));

        testST2(734_561, SysTime(DateTime(2012, 2, 28, 12, 2, 9), msecs(212)));
        testST2(734_562, SysTime(DateTime(2012, 2, 29, 12, 2, 9), msecs(212)));
        testST2(734_563, SysTime(DateTime(2012, 3, 1, 12, 2, 9), msecs(212)));

        //Test B.C.
        testST2(0, SysTime(DateTime(0, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-1, SysTime(DateTime(0, 12, 30, 12, 2, 9), msecs(212)));
        testST2(-30, SysTime(DateTime(0, 12, 1, 12, 2, 9), msecs(212)));
        testST2(-31, SysTime(DateTime(0, 11, 30, 12, 2, 9), msecs(212)));

        testST2(-366, SysTime(DateTime(-1, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-367, SysTime(DateTime(-1, 12, 30, 12, 2, 9), msecs(212)));
        testST2(-730, SysTime(DateTime(-1, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-731, SysTime(DateTime(-2, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-1095, SysTime(DateTime(-2, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-1096, SysTime(DateTime(-3, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-1460, SysTime(DateTime(-3, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-1461, SysTime(DateTime(-4, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-1826, SysTime(DateTime(-4, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-1827, SysTime(DateTime(-5, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-2191, SysTime(DateTime(-5, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-3652, SysTime(DateTime(-9, 1, 1, 12, 2, 9), msecs(212)));

        testST2(-18_262, SysTime(DateTime(-49, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-18_627, SysTime(DateTime(-50, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-35_794, SysTime(DateTime(-97, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-36_160, SysTime(DateTime(-99, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-36_524, SysTime(DateTime(-99, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-36_889, SysTime(DateTime(-100, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-37_254, SysTime(DateTime(-101, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-38_715, SysTime(DateTime(-105, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-73_413, SysTime(DateTime(-200, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-73_778, SysTime(DateTime(-201, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-109_937, SysTime(DateTime(-300, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-110_302, SysTime(DateTime(-301, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-146_097, SysTime(DateTime(-400, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-146_462, SysTime(DateTime(-400, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-146_827, SysTime(DateTime(-401, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-182_621, SysTime(DateTime(-499, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-182_986, SysTime(DateTime(-500, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-183_351, SysTime(DateTime(-501, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-365_607, SysTime(DateTime(-1000, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-365_972, SysTime(DateTime(-1001, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-584_387, SysTime(DateTime(-1599, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-584_388, SysTime(DateTime(-1600, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-584_753, SysTime(DateTime(-1600, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-585_118, SysTime(DateTime(-1601, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-694_325, SysTime(DateTime(-1900, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-694_690, SysTime(DateTime(-1901, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-730_484, SysTime(DateTime(-1999, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-730_485, SysTime(DateTime(-2000, 12, 31, 12, 2, 9), msecs(212)));
        testST2(-730_850, SysTime(DateTime(-2000, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-731_215, SysTime(DateTime(-2001, 1, 1, 12, 2, 9), msecs(212)));

        testST2(-734_502, SysTime(DateTime(-2010, 1, 1, 12, 2, 9), msecs(212)));
        testST2(-734_472, SysTime(DateTime(-2010, 1, 31, 12, 2, 9), msecs(212)));
        testST2(-734_471, SysTime(DateTime(-2010, 2, 1, 12, 2, 9), msecs(212)));
        testST2(-734_444, SysTime(DateTime(-2010, 2, 28, 12, 2, 9), msecs(212)));
        testST2(-734_443, SysTime(DateTime(-2010, 3, 1, 12, 2, 9), msecs(212)));
        testST2(-734_413, SysTime(DateTime(-2010, 3, 31, 12, 2, 9), msecs(212)));
        testST2(-734_412, SysTime(DateTime(-2010, 4, 1, 12, 2, 9), msecs(212)));
        testST2(-734_383, SysTime(DateTime(-2010, 4, 30, 12, 2, 9), msecs(212)));
        testST2(-734_382, SysTime(DateTime(-2010, 5, 1, 12, 2, 9), msecs(212)));
        testST2(-734_352, SysTime(DateTime(-2010, 5, 31, 12, 2, 9), msecs(212)));
        testST2(-734_351, SysTime(DateTime(-2010, 6, 1, 12, 2, 9), msecs(212)));
        testST2(-734_322, SysTime(DateTime(-2010, 6, 30, 12, 2, 9), msecs(212)));
        testST2(-734_321, SysTime(DateTime(-2010, 7, 1, 12, 2, 9), msecs(212)));
        testST2(-734_291, SysTime(DateTime(-2010, 7, 31, 12, 2, 9), msecs(212)));
        testST2(-734_290, SysTime(DateTime(-2010, 8, 1, 12, 2, 9), msecs(212)));
        testST2(-734_260, SysTime(DateTime(-2010, 8, 31, 12, 2, 9), msecs(212)));
        testST2(-734_259, SysTime(DateTime(-2010, 9, 1, 12, 2, 9), msecs(212)));
        testST2(-734_230, SysTime(DateTime(-2010, 9, 30, 12, 2, 9), msecs(212)));
        testST2(-734_229, SysTime(DateTime(-2010, 10, 1, 12, 2, 9), msecs(212)));
        testST2(-734_199, SysTime(DateTime(-2010, 10, 31, 12, 2, 9), msecs(212)));
        testST2(-734_198, SysTime(DateTime(-2010, 11, 1, 12, 2, 9), msecs(212)));
        testST2(-734_169, SysTime(DateTime(-2010, 11, 30, 12, 2, 9), msecs(212)));
        testST2(-734_168, SysTime(DateTime(-2010, 12, 1, 12, 2, 9), msecs(212)));
        testST2(-734_138, SysTime(DateTime(-2010, 12, 31, 12, 2, 9), msecs(212)));

        testST2(-735_202, SysTime(DateTime(-2012, 2, 1, 12, 2, 9), msecs(212)));
        testST2(-735_175, SysTime(DateTime(-2012, 2, 28, 12, 2, 9), msecs(212)));
        testST2(-735_174, SysTime(DateTime(-2012, 2, 29, 12, 2, 9), msecs(212)));
        testST2(-735_173, SysTime(DateTime(-2012, 3, 1, 12, 2, 9), msecs(212)));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        static assert(!__traits(compiles, cst.dayOfGregorianCal = 7));
        //static assert(!__traits(compiles, ist.dayOfGregorianCal = 7));
    }


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

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

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(st.isoWeek == 27);
        assert(cst.isoWeek == 27);
        //assert(ist.isoWeek == 27);
    }


    /++
        $(LREF SysTime) for the last day in the month that this Date is in.
        The time portion of endOfMonth is always 23:59:59.9999999.
      +/
    @property SysTime endOfMonth() @safe const nothrow
    {
        immutable hnsecs = adjTime;
        immutable days = getUnitsFromHNSecs!"days"(hnsecs);

        auto date = Date(cast(int)days + 1).endOfMonth;
        auto newDays = date.dayOfGregorianCal - 1;
        long theTimeHNSecs;

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

        immutable newDaysHNSecs = convert!("days", "hnsecs")(newDays);

        auto retval = SysTime(this._stdTime, this._timezone);
        retval.adjTime = newDaysHNSecs + theTimeHNSecs;

        return retval;
    }

    ///
    unittest
    {
        assert(SysTime(DateTime(1999, 1, 6, 0, 0, 0)).endOfMonth ==
               SysTime(DateTime(1999, 1, 31, 23, 59, 59),
                       hnsecs(9_999_999)));

        assert(SysTime(DateTime(1999, 2, 7, 19, 30, 0),
                       msecs(24)).endOfMonth ==
               SysTime(DateTime(1999, 2, 28, 23, 59, 59),
                       hnsecs(9_999_999)));

        assert(SysTime(DateTime(2000, 2, 7, 5, 12, 27),
                       usecs(5203)).endOfMonth ==
               SysTime(DateTime(2000, 2, 29, 23, 59, 59),
                       hnsecs(9_999_999)));

        assert(SysTime(DateTime(2000, 6, 4, 12, 22, 9),
                       hnsecs(12345)).endOfMonth ==
               SysTime(DateTime(2000, 6, 30, 23, 59, 59),
                       hnsecs(9_999_999)));
    }

    unittest
    {
        //Test A.D.
        assert(SysTime(Date(1999, 1, 1)).endOfMonth == SysTime(DateTime(1999, 1, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 2, 1)).endOfMonth == SysTime(DateTime(1999, 2, 28, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(2000, 2, 1)).endOfMonth == SysTime(DateTime(2000, 2, 29, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 3, 1)).endOfMonth == SysTime(DateTime(1999, 3, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 4, 1)).endOfMonth == SysTime(DateTime(1999, 4, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 5, 1)).endOfMonth == SysTime(DateTime(1999, 5, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 6, 1)).endOfMonth == SysTime(DateTime(1999, 6, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 7, 1)).endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 8, 1)).endOfMonth == SysTime(DateTime(1999, 8, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 9, 1)).endOfMonth == SysTime(DateTime(1999, 9, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 10, 1)).endOfMonth == SysTime(DateTime(1999, 10, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 11, 1)).endOfMonth == SysTime(DateTime(1999, 11, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(1999, 12, 1)).endOfMonth == SysTime(DateTime(1999, 12, 31, 23, 59, 59), hnsecs(9_999_999)));

        //Test B.C.
        assert(SysTime(Date(-1999, 1, 1)).endOfMonth == SysTime(DateTime(-1999, 1, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 2, 1)).endOfMonth == SysTime(DateTime(-1999, 2, 28, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-2000, 2, 1)).endOfMonth == SysTime(DateTime(-2000, 2, 29, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 3, 1)).endOfMonth == SysTime(DateTime(-1999, 3, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 4, 1)).endOfMonth == SysTime(DateTime(-1999, 4, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 5, 1)).endOfMonth == SysTime(DateTime(-1999, 5, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 6, 1)).endOfMonth == SysTime(DateTime(-1999, 6, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 7, 1)).endOfMonth == SysTime(DateTime(-1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 8, 1)).endOfMonth == SysTime(DateTime(-1999, 8, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 9, 1)).endOfMonth == SysTime(DateTime(-1999, 9, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 10, 1)).endOfMonth ==
               SysTime(DateTime(-1999, 10, 31, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 11, 1)).endOfMonth ==
               SysTime(DateTime(-1999, 11, 30, 23, 59, 59), hnsecs(9_999_999)));
        assert(SysTime(Date(-1999, 12, 1)).endOfMonth ==
               SysTime(DateTime(-1999, 12, 31, 23, 59, 59), hnsecs(9_999_999)));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
        //assert(ist.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999)));
    }


    /++
        The last day in the month that this $(LREF SysTime) is in.
      +/
    @property ubyte daysInMonth() @safe const nothrow
    {
        return Date(dayOfGregorianCal).daysInMonth;
    }

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

    unittest
    {
        //Test A.D.
        assert(SysTime(DateTime(1999, 1, 1, 12, 1, 13)).daysInMonth == 31);
        assert(SysTime(DateTime(1999, 2, 1, 17, 13, 12)).daysInMonth == 28);
        assert(SysTime(DateTime(2000, 2, 1, 13, 2, 12)).daysInMonth == 29);
        assert(SysTime(DateTime(1999, 3, 1, 12, 13, 12)).daysInMonth == 31);
        assert(SysTime(DateTime(1999, 4, 1, 12, 6, 13)).daysInMonth == 30);
        assert(SysTime(DateTime(1999, 5, 1, 15, 13, 12)).daysInMonth == 31);
        assert(SysTime(DateTime(1999, 6, 1, 13, 7, 12)).daysInMonth == 30);
        assert(SysTime(DateTime(1999, 7, 1, 12, 13, 17)).daysInMonth == 31);
        assert(SysTime(DateTime(1999, 8, 1, 12, 3, 13)).daysInMonth == 31);
        assert(SysTime(DateTime(1999, 9, 1, 12, 13, 12)).daysInMonth == 30);
        assert(SysTime(DateTime(1999, 10, 1, 13, 19, 12)).daysInMonth == 31);
        assert(SysTime(DateTime(1999, 11, 1, 12, 13, 17)).daysInMonth == 30);
        assert(SysTime(DateTime(1999, 12, 1, 12, 52, 13)).daysInMonth == 31);

        //Test B.C.
        assert(SysTime(DateTime(-1999, 1, 1, 12, 1, 13)).daysInMonth == 31);
        assert(SysTime(DateTime(-1999, 2, 1, 7, 13, 12)).daysInMonth == 28);
        assert(SysTime(DateTime(-2000, 2, 1, 13, 2, 12)).daysInMonth == 29);
        assert(SysTime(DateTime(-1999, 3, 1, 12, 13, 12)).daysInMonth == 31);
        assert(SysTime(DateTime(-1999, 4, 1, 12, 6, 13)).daysInMonth == 30);
        assert(SysTime(DateTime(-1999, 5, 1, 5, 13, 12)).daysInMonth == 31);
        assert(SysTime(DateTime(-1999, 6, 1, 13, 7, 12)).daysInMonth == 30);
        assert(SysTime(DateTime(-1999, 7, 1, 12, 13, 17)).daysInMonth == 31);
        assert(SysTime(DateTime(-1999, 8, 1, 12, 3, 13)).daysInMonth == 31);
        assert(SysTime(DateTime(-1999, 9, 1, 12, 13, 12)).daysInMonth == 30);
        assert(SysTime(DateTime(-1999, 10, 1, 13, 19, 12)).daysInMonth == 31);
        assert(SysTime(DateTime(-1999, 11, 1, 12, 13, 17)).daysInMonth == 30);
        assert(SysTime(DateTime(-1999, 12, 1, 12, 52, 13)).daysInMonth == 31);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.daysInMonth == 31);
        //assert(ist.daysInMonth == 31);
    }


    /++
        Whether the current year is a date in A.D.
      +/
    @property bool isAD() @safe const nothrow
    {
        return adjTime >= 0;
    }

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

    unittest
    {
        assert(SysTime(DateTime(2010, 7, 4, 12, 0, 9)).isAD);
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).isAD);
        assert(!SysTime(DateTime(0, 12, 31, 23, 59, 59)).isAD);
        assert(!SysTime(DateTime(0, 1, 1, 23, 59, 59)).isAD);
        assert(!SysTime(DateTime(-1, 1, 1, 23 ,59 ,59)).isAD);
        assert(!SysTime(DateTime(-2010, 7, 4, 12, 2, 2)).isAD);

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.isAD);
        //assert(ist.isAD);
    }


    /++
        The $(WEB en.wikipedia.org/wiki/Julian_day, Julian day)
        for this $(LREF SysTime) 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() @safe const nothrow
    {
        immutable jd = dayOfGregorianCal + 1_721_425;

        return hour < 12 ? jd - 1 : jd;
    }

    unittest
    {
        assert(SysTime(DateTime(-4713, 11, 24, 0, 0, 0)).julianDay == -1);
        assert(SysTime(DateTime(-4713, 11, 24, 12, 0, 0)).julianDay == 0);

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

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

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

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

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

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

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

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.julianDay == 2_451_366);
        //assert(ist.julianDay == 2_451_366);
    }


    /++
        The modified $(WEB 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() @safe const nothrow
    {
        return (dayOfGregorianCal + 1_721_425) - 2_400_001;
    }

    unittest
    {
        assert(SysTime(DateTime(1858, 11, 17, 0, 0, 0)).modJulianDay == 0);
        assert(SysTime(DateTime(1858, 11, 17, 12, 0, 0)).modJulianDay == 0);

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

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cst.modJulianDay == 51_365);
        //assert(ist.modJulianDay == 51_365);
    }


    /++
        Returns a $(LREF Date) equivalent to this $(LREF SysTime).
      +/
    Date opCast(T)() @safe const nothrow
        if(is(Unqual!T == Date))
    {
        return Date(dayOfGregorianCal);
    }

    unittest
    {
        assert(cast(Date)SysTime(Date(1999, 7, 6)) == Date(1999, 7, 6));
        assert(cast(Date)SysTime(Date(2000, 12, 31)) == Date(2000, 12, 31));
        assert(cast(Date)SysTime(Date(2001, 1, 1)) == Date(2001, 1, 1));

        assert(cast(Date)SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == Date(1999, 7, 6));
        assert(cast(Date)SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == Date(2000, 12, 31));
        assert(cast(Date)SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == Date(2001, 1, 1));

        assert(cast(Date)SysTime(Date(-1999, 7, 6)) == Date(-1999, 7, 6));
        assert(cast(Date)SysTime(Date(-2000, 12, 31)) == Date(-2000, 12, 31));
        assert(cast(Date)SysTime(Date(-2001, 1, 1)) == Date(-2001, 1, 1));

        assert(cast(Date)SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == Date(-1999, 7, 6));
        assert(cast(Date)SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == Date(-2000, 12, 31));
        assert(cast(Date)SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == Date(-2001, 1, 1));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cast(Date)cst != Date.init);
        //assert(cast(Date)ist != Date.init);
    }


    /++
        Returns a $(LREF DateTime) equivalent to this $(LREF SysTime).
      +/
    DateTime opCast(T)() @safe const nothrow
        if(is(Unqual!T == DateTime))
    {
        try
        {
            auto hnsecs = adjTime;
            auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1;

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

            immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
            immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
            immutable second = getUnitsFromHNSecs!"seconds"(hnsecs);

            return DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
        }
        catch(Exception e)
            assert(0, "Either DateTime's constructor or TimeOfDay's constructor threw.");
    }

    unittest
    {
        assert(cast(DateTime)SysTime(DateTime(1, 1, 6, 7, 12, 22)) == DateTime(1, 1, 6, 7, 12, 22));
        assert(cast(DateTime)SysTime(DateTime(1, 1, 6, 7, 12, 22), msecs(22)) == DateTime(1, 1, 6, 7, 12, 22));
        assert(cast(DateTime)SysTime(Date(1999, 7, 6)) == DateTime(1999, 7, 6, 0, 0, 0));
        assert(cast(DateTime)SysTime(Date(2000, 12, 31)) == DateTime(2000, 12, 31, 0, 0, 0));
        assert(cast(DateTime)SysTime(Date(2001, 1, 1)) == DateTime(2001, 1, 1, 0, 0, 0));

        assert(cast(DateTime)SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == DateTime(1999, 7, 6, 12, 10, 9));
        assert(cast(DateTime)SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == DateTime(2000, 12, 31, 13, 11, 10));
        assert(cast(DateTime)SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == DateTime(2001, 1, 1, 14, 12, 11));

        assert(cast(DateTime)SysTime(DateTime(-1, 1, 6, 7, 12, 22)) == DateTime(-1, 1, 6, 7, 12, 22));
        assert(cast(DateTime)SysTime(DateTime(-1, 1, 6, 7, 12, 22), msecs(22)) == DateTime(-1, 1, 6, 7, 12, 22));
        assert(cast(DateTime)SysTime(Date(-1999, 7, 6)) == DateTime(-1999, 7, 6, 0, 0, 0));
        assert(cast(DateTime)SysTime(Date(-2000, 12, 31)) == DateTime(-2000, 12, 31, 0, 0, 0));
        assert(cast(DateTime)SysTime(Date(-2001, 1, 1)) == DateTime(-2001, 1, 1, 0, 0, 0));

        assert(cast(DateTime)SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == DateTime(-1999, 7, 6, 12, 10, 9));
        assert(cast(DateTime)SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == DateTime(-2000, 12, 31, 13, 11, 10));
        assert(cast(DateTime)SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == DateTime(-2001, 1, 1, 14, 12, 11));

        assert(cast(DateTime)SysTime(DateTime(2011, 1, 13, 8, 17, 2), msecs(296), LocalTime()) ==
               DateTime(2011, 1, 13, 8, 17, 2));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cast(DateTime)cst != DateTime.init);
        //assert(cast(DateTime)ist != DateTime.init);
    }


    /++
        Returns a $(LREF TimeOfDay) equivalent to this $(LREF SysTime).
      +/
    TimeOfDay opCast(T)() @safe const nothrow
        if(is(Unqual!T == TimeOfDay))
    {
        try
        {
            auto hnsecs = adjTime;
            hnsecs = removeUnitsFromHNSecs!"days"(hnsecs);

            if(hnsecs < 0)
                hnsecs += convert!("hours", "hnsecs")(24);

            immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs);
            immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
            immutable second = getUnitsFromHNSecs!"seconds"(hnsecs);

            return TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second);
        }
        catch(Exception e)
            assert(0, "TimeOfDay's constructor threw.");
    }

    unittest
    {
        assert(cast(TimeOfDay)SysTime(Date(1999, 7, 6)) == TimeOfDay(0, 0, 0));
        assert(cast(TimeOfDay)SysTime(Date(2000, 12, 31)) == TimeOfDay(0, 0, 0));
        assert(cast(TimeOfDay)SysTime(Date(2001, 1, 1)) == TimeOfDay(0, 0, 0));

        assert(cast(TimeOfDay)SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == TimeOfDay(12, 10, 9));
        assert(cast(TimeOfDay)SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == TimeOfDay(13, 11, 10));
        assert(cast(TimeOfDay)SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == TimeOfDay(14, 12, 11));

        assert(cast(TimeOfDay)SysTime(Date(-1999, 7, 6)) == TimeOfDay(0, 0, 0));
        assert(cast(TimeOfDay)SysTime(Date(-2000, 12, 31)) == TimeOfDay(0, 0, 0));
        assert(cast(TimeOfDay)SysTime(Date(-2001, 1, 1)) == TimeOfDay(0, 0, 0));

        assert(cast(TimeOfDay)SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == TimeOfDay(12, 10, 9));
        assert(cast(TimeOfDay)SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == TimeOfDay(13, 11, 10));
        assert(cast(TimeOfDay)SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == TimeOfDay(14, 12, 11));

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cast(TimeOfDay)cst != TimeOfDay.init);
        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
    }


    //Temporary hack until bug http://d.puremagic.com/issues/show_bug.cgi?id=4867 is fixed.
    //This allows assignment from const(SysTime) to SysTime.
    //It may be a good idea to keep it though, since casting from a type to itself
    //should be allowed, and it doesn't work without this opCast() since opCast()
    //has already been defined for other types.
    SysTime opCast(T)() @safe const pure nothrow
        if(is(Unqual!T == SysTime))
    {
        return SysTime(_stdTime, _timezone);
    }


    /++
        Converts this $(LREF SysTime) to a string with the format
        YYYYMMDDTHHMMSS.FFFFFFFTZ (where F is fractional seconds and TZ is time
        zone).

        Note that the number of digits in the fractional seconds varies with the
        number of fractional seconds. It's a maximum of 7 (which would be
        hnsecs), but only has as many as are necessary to hold the correct value
        (so no trailing zeroes), and if there are no fractional seconds, then
        there is no decimal point.

        If this $(LREF SysTime)'s time zone is $(LREF LocalTime), then TZ is empty.
        If its time zone is $(D UTC), then it is "Z". Otherwise, it is the
        offset from UTC (e.g. +0100 or -0700). Note that the offset from UTC
        is $(I not) enough to uniquely identify the time zone.

        Time zone offsets will be in the form +HHMM or -HHMM.

        $(RED Warning:
            Previously, toISOString did the same as $(LREF toISOExtString) and
            generated +HH:MM or -HH:MM for the time zone when it was not
            $(LREF LocalTime) or $(LREF UTC), which is not in conformance with
            ISO 9601 for the non-extended string format. This has now been
            fixed. However, for now, fromISOString will continue to accept the
            extended format for the time zone so that any code which has been
            writing out the result of toISOString to read in later will continue
            to work.)
      +/
    string toISOString() @safe const nothrow
    {
        import std.format : format;
        try
        {
            immutable adjustedTime = adjTime;
            long hnsecs = adjustedTime;

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

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

            auto hour = splitUnitsFromHNSecs!"hours"(hnsecs);
            auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
            auto second = splitUnitsFromHNSecs!"seconds"(hnsecs);

            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
            auto fracSecStr = fracSecsToISOString(cast(int)hnsecs);

            if(_timezone is LocalTime())
                return dateTime.toISOString() ~ fracSecsToISOString(cast(int)hnsecs);

            if(_timezone is UTC())
                return dateTime.toISOString() ~ fracSecsToISOString(cast(int)hnsecs) ~ "Z";

            immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime);

            return format("%s%s%s",
                          dateTime.toISOString(),
                          fracSecsToISOString(cast(int)hnsecs),
                          SimpleTimeZone.toISOExtString(utcOffset));
        }
        catch(Exception e)
            assert(0, "format() threw.");
    }

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

        assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0),
                       msecs(24)).toISOString() ==
               "19981225T021500.024");

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

        assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2),
                       hnsecs(520_920)).toISOString() ==
               "-00040105T000002.052092");
    }

    unittest
    {
        //Test A.D.
        assert(SysTime(DateTime.init, UTC()).toISOString() == "00010101T000000Z");
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toISOString() == "00010101T000000.0000001Z");

        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toISOString() == "00091204T000000");
        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toISOString() == "00991204T050612");
        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toISOString() == "09991204T134459");
        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toISOString() == "99990704T235959");
        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toISOString() == "+100001020T010101");

        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toISOString() == "00091204T000000.042");
        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toISOString() == "00991204T050612.1");
        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toISOString() == "09991204T134459.04502");
        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOString() == "99990704T235959.0000012");
        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOString() == "+100001020T010101.050789");

        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
                                 new immutable SimpleTimeZone(dur!"minutes"(-360))).toISOString() ==
                        "20121221T121212-06:00");

        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
                                 new immutable SimpleTimeZone(dur!"minutes"(420))).toISOString() ==
                        "20121221T121212+07:00");

        //Test B.C.
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toISOString() ==
               "00001231T235959.9999999Z");
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toISOString() == "00001231T235959.0000001Z");
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toISOString() == "00001231T235959Z");

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

        assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toISOString() == "00001204T000000.007");
        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toISOString() == "-00091204T000000.042");
        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toISOString() == "-00991204T050612.1");
        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toISOString() == "-09991204T134459.04502");
        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOString() == "-99990704T235959.0000012");
        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOString() == "-100001020T010101.050789");

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cast(TimeOfDay)cst != TimeOfDay.init);
        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
    }



    /++
        Converts this $(LREF SysTime) to a string with the format
        YYYY-MM-DDTHH:MM:SS.FFFFFFFTZ (where F is fractional seconds and TZ
        is the time zone).

        Note that the number of digits in the fractional seconds varies with the
        number of fractional seconds. It's a maximum of 7 (which would be
        hnsecs), but only has as many as are necessary to hold the correct value
        (so no trailing zeroes), and if there are no fractional seconds, then
        there is no decimal point.

        If this $(LREF SysTime)'s time zone is $(LREF LocalTime), then TZ is empty. If
        its time zone is $(D UTC), then it is "Z". Otherwise, it is the offset
        from UTC (e.g. +01:00 or -07:00). Note that the offset from UTC is
        $(I not) enough to uniquely identify the time zone.

        Time zone offsets will be in the form +HH:MM or -HH:MM.
      +/
    string toISOExtString() @safe const nothrow
    {
        import std.format : format;
        try
        {
            immutable adjustedTime = adjTime;
            long hnsecs = adjustedTime;

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

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

            auto hour = splitUnitsFromHNSecs!"hours"(hnsecs);
            auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
            auto second = splitUnitsFromHNSecs!"seconds"(hnsecs);

            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
            auto fracSecStr = fracSecsToISOString(cast(int)hnsecs);

            if(_timezone is LocalTime())
                return dateTime.toISOExtString() ~ fracSecsToISOString(cast(int)hnsecs);

            if(_timezone is UTC())
                return dateTime.toISOExtString() ~ fracSecsToISOString(cast(int)hnsecs) ~ "Z";

            immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime);

            return format("%s%s%s",
                          dateTime.toISOExtString(),
                          fracSecsToISOString(cast(int)hnsecs),
                          SimpleTimeZone.toISOExtString(utcOffset));
        }
        catch(Exception e)
            assert(0, "format() threw.");
    }

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

        assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0),
                       msecs(24)).toISOExtString() ==
               "1998-12-25T02:15:00.024");

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

        assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2),
                       hnsecs(520_920)).toISOExtString() ==
               "-0004-01-05T00:00:02.052092");
    }

    unittest
    {
        //Test A.D.
        assert(SysTime(DateTime.init, UTC()).toISOExtString() == "0001-01-01T00:00:00Z");
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toISOExtString() ==
               "0001-01-01T00:00:00.0000001Z");

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

        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toISOExtString() == "0009-12-04T00:00:00.042");
        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toISOExtString() == "0099-12-04T05:06:12.1");
        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toISOExtString() == "0999-12-04T13:44:59.04502");
        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOExtString() == "9999-07-04T23:59:59.0000012");
        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOExtString() ==
               "+10000-10-20T01:01:01.050789");

        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
                                new immutable SimpleTimeZone(dur!"minutes"(-360))).toISOExtString() ==
               "2012-12-21T12:12:12-06:00");

        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
                       new immutable SimpleTimeZone(dur!"minutes"(420))).toISOExtString() ==
               "2012-12-21T12:12:12+07:00");

        //Test B.C.
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toISOExtString() ==
               "0000-12-31T23:59:59.9999999Z");
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toISOExtString() ==
               "0000-12-31T23:59:59.0000001Z");
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toISOExtString() == "0000-12-31T23:59:59Z");

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

        assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toISOExtString() == "0000-12-04T00:00:00.007");
        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toISOExtString() == "-0009-12-04T00:00:00.042");
        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toISOExtString() == "-0099-12-04T05:06:12.1");
        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toISOExtString() ==
               "-0999-12-04T13:44:59.04502");
        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOExtString() ==
               "-9999-07-04T23:59:59.0000012");
        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOExtString() ==
               "-10000-10-20T01:01:01.050789");

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cast(TimeOfDay)cst != TimeOfDay.init);
        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
    }

    /++
        Converts this $(LREF SysTime) to a string with the format
        YYYY-Mon-DD HH:MM:SS.FFFFFFFTZ (where F is fractional seconds and TZ
        is the time zone).

        Note that the number of digits in the fractional seconds varies with the
        number of fractional seconds. It's a maximum of 7 (which would be
        hnsecs), but only has as many as are necessary to hold the correct value
        (so no trailing zeroes), and if there are no fractional seconds, then
        there is no decimal point.

        If this $(LREF SysTime)'s time zone is $(LREF LocalTime), then TZ is empty. If
        its time zone is $(D UTC), then it is "Z". Otherwise, it is the offset
        from UTC (e.g. +01:00 or -07:00). Note that the offset from UTC is
        $(I not) enough to uniquely identify the time zone.

        Time zone offsets will be in the form +HH:MM or -HH:MM.
      +/
    string toSimpleString() @safe const nothrow
    {
        import std.format : format;
        try
        {
            immutable adjustedTime = adjTime;
            long hnsecs = adjustedTime;

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

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

            auto hour = splitUnitsFromHNSecs!"hours"(hnsecs);
            auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs);
            auto second = splitUnitsFromHNSecs!"seconds"(hnsecs);

            auto dateTime = DateTime(Date(cast(int)days), TimeOfDay(cast(int)hour, cast(int)minute, cast(int)second));
            auto fracSecStr = fracSecsToISOString(cast(int)hnsecs);

            if(_timezone is LocalTime())
                return dateTime.toSimpleString() ~ fracSecsToISOString(cast(int)hnsecs);

            if(_timezone is UTC())
                return dateTime.toSimpleString() ~ fracSecsToISOString(cast(int)hnsecs) ~ "Z";

            immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime);

            return format("%s%s%s",
                          dateTime.toSimpleString(),
                          fracSecsToISOString(cast(int)hnsecs),
                          SimpleTimeZone.toISOExtString(utcOffset));
        }
        catch(Exception e)
            assert(0, "format() threw.");
    }

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

        assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0),
                       msecs(24)).toSimpleString() ==
               "1998-Dec-25 02:15:00.024");

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

        assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2),
                       hnsecs(520_920)).toSimpleString() ==
                "-0004-Jan-05 00:00:02.052092");
    }

    unittest
    {
        //Test A.D.
        assert(SysTime(DateTime.init, UTC()).toString() == "0001-Jan-01 00:00:00Z");
        assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toString() == "0001-Jan-01 00:00:00.0000001Z");

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

        assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toSimpleString() == "0009-Dec-04 00:00:00.042");
        assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toSimpleString() == "0099-Dec-04 05:06:12.1");
        assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toSimpleString() ==
               "0999-Dec-04 13:44:59.04502");
        assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toSimpleString() ==
               "9999-Jul-04 23:59:59.0000012");
        assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toSimpleString() ==
               "+10000-Oct-20 01:01:01.050789");

        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
                       new immutable SimpleTimeZone(dur!"minutes"(-360))).toSimpleString() ==
               "2012-Dec-21 12:12:12-06:00");

        assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12),
                       new immutable SimpleTimeZone(dur!"minutes"(420))).toSimpleString() ==
               "2012-Dec-21 12:12:12+07:00");

        //Test B.C.
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toSimpleString() ==
               "0000-Dec-31 23:59:59.9999999Z");
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toSimpleString() ==
               "0000-Dec-31 23:59:59.0000001Z");
        assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toSimpleString() == "0000-Dec-31 23:59:59Z");

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

        assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toSimpleString() == "0000-Dec-04 00:00:00.007");
        assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toSimpleString() == "-0009-Dec-04 00:00:00.042");
        assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toSimpleString() == "-0099-Dec-04 05:06:12.1");
        assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toSimpleString() ==
               "-0999-Dec-04 13:44:59.04502");
        assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toSimpleString() ==
               "-9999-Jul-04 23:59:59.0000012");
        assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toSimpleString() ==
               "-10000-Oct-20 01:01:01.050789");

        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(cast(TimeOfDay)cst != TimeOfDay.init);
        //assert(cast(TimeOfDay)ist != TimeOfDay.init);
    }


    /++
        Converts this $(LREF SysTime) to a string.
      +/
    string toString() @safe const nothrow
    {
        return toSimpleString();
    }

    unittest
    {
        auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33));
        assert(st.toString());
        assert(cst.toString());
        //assert(ist.toString());
    }


    /++
        Creates a $(LREF SysTime) from a string with the format
        YYYYMMDDTHHMMSS.FFFFFFFTZ (where F is fractional seconds is the time
        zone). Whitespace is stripped from the given string.

        The exact format is exactly as described in $(D toISOString) except that
        trailing zeroes are permitted - including having fractional seconds with
        all zeroes. However, a decimal point with nothing following it is
        invalid.

        If there is no time zone in the string, then $(LREF LocalTime) is used.
        If the time zone is "Z", then $(D UTC) is used. Otherwise, a
        $(LREF SimpleTimeZone) which corresponds to the given offset from UTC is
        used. To get the returned $(LREF SysTime) to be a particular time
        zone, pass in that time zone and the $(LREF SysTime) to be returned
        will be converted to that time zone (though it will still be read in as
        whatever time zone is in its string).

        The accepted formats for time zone offsets are +HH, -HH, +HHMM, and
        -HHMM.

        $(RED Warning:
            Previously, $(LREF toISOString) did the same as
            $(LREF toISOExtString) and generated +HH:MM or -HH:MM for the time
            zone when it was not $(LREF LocalTime) or $(LREF UTC), which is not
            in conformance with ISO 9601 for the non-extended string format.
            This has now been fixed. However, for now, fromISOString will
            continue to accept the extended format for the time zone so that any
            code which has been writing out the result of toISOString to read in
            later will continue to work.)

        Params:
            isoString = A string formatted in the ISO format for dates and times.
            tz        = The time zone to convert the given time to (no
                        conversion occurs if null).

        Throws:
            $(LREF DateTimeException) if the given string is not in the ISO
            format or if the resulting $(LREF SysTime) would not be valid.
      +/
    static SysTime fromISOString(S)(in S isoString, immutable TimeZone tz = null) @safe
        if(isSomeString!S)
    {
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : startsWith, find;
        import std.format : format;

        auto dstr = to!dstring(strip(isoString));
        immutable skipFirst = dstr.startsWith('+', '-') != 0;

        auto found = (skipFirst ? dstr[1..$] : dstr).find('.', 'Z', '+', '-');
        auto dateTimeStr = dstr[0 .. $ - found[0].length];

        dstring fracSecStr;
        dstring zoneStr;

        if(found[1] != 0)
        {
            if(found[1] == 1)
            {
                auto foundTZ = found[0].find('Z', '+', '-');

                if(foundTZ[1] != 0)
                {
                    fracSecStr = found[0][0 .. $ - foundTZ[0].length];
                    zoneStr = foundTZ[0];
                }
                else
                    fracSecStr = found[0];
            }
            else
                zoneStr = found[0];
        }

        try
        {
            auto dateTime = DateTime.fromISOString(dateTimeStr);
            auto fracSec = fracSecsFromISOString(fracSecStr);
            Rebindable!(immutable TimeZone) parsedZone;

            if(zoneStr.empty)
                parsedZone = LocalTime();
            else if(zoneStr == "Z")
                parsedZone = UTC();
            else
            {
                try
                    parsedZone = SimpleTimeZone.fromISOString(zoneStr);
                catch(DateTimeException dte)
                    parsedZone = SimpleTimeZone.fromISOExtString(zoneStr);
            }

            auto retval = SysTime(dateTime, fracSec, parsedZone);

            if(tz !is null)
                retval.timezone = tz;

            return retval;
        }
        catch(DateTimeException dte)
            throw new DateTimeException(format("Invalid ISO String: %s", isoString));
    }

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

        assert(SysTime.fromISOString("19981225T021500.007") ==
               SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7)));

        assert(SysTime.fromISOString("00000105T230959.00002") ==
               SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20)));

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

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

        assert(SysTime.fromISOString("20100704T070612Z") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC()));

        assert(SysTime.fromISOString("20100704T070612-0800") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
                       new immutable SimpleTimeZone(hours(-8))));

        assert(SysTime.fromISOString("20100704T070612+0800") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
                       new immutable SimpleTimeZone(hours(8))));
    }

    unittest
    {
        import std.format: format;

        foreach(str; ["", "20100704000000", "20100704 000000", "20100704t000000",
                      "20100704T000000.", "20100704T000000.A", "20100704T000000.Z",
                      "20100704T000000.00000000", "20100704T000000.00000000",
                      "20100704T000000+", "20100704T000000-", "20100704T000000:",
                      "20100704T000000-:", "20100704T000000+:", "20100704T000000-1:",
                      "20100704T000000+1:", "20100704T000000+1:0",
                      "20100704T000000-12.00", "20100704T000000+12.00",
                      "20100704T000000-8", "20100704T000000+8",
                      "20100704T000000-800", "20100704T000000+800",
                      "20100704T000000-080", "20100704T000000+080",
                      "20100704T000000-2400", "20100704T000000+2400",
                      "20100704T000000-1260", "20100704T000000+1260",
                      "20100704T000000.0-8", "20100704T000000.0+8",
                      "20100704T000000.0-800", "20100704T000000.0+800",
                      "20100704T000000.0-080", "20100704T000000.0+080",
                      "20100704T000000.0-2400", "20100704T000000.0+2400",
                      "20100704T000000.0-1260", "20100704T000000.0+1260",
                      "20100704T000000-8:00", "20100704T000000+8:00",
                      "20100704T000000-08:0", "20100704T000000+08:0",
                      "20100704T000000-24:00", "20100704T000000+24:00",
                      "20100704T000000-12:60", "20100704T000000+12:60",
                      "20100704T000000.0-8:00", "20100704T000000.0+8:00",
                      "20100704T000000.0-08:0", "20100704T000000.0+08:0",
                      "20100704T000000.0-24:00", "20100704T000000.0+24:00",
                      "20100704T000000.0-12:60", "20100704T000000.0+12:60",
                      "2010-07-0400:00:00", "2010-07-04 00:00:00",
                      "2010-07-04t00:00:00", "2010-07-04T00:00:00.",
                      "2010-Jul-0400:00:00", "2010-Jul-04 00:00:00", "2010-Jul-04t00:00:00",
                      "2010-Jul-04T00:00:00", "2010-Jul-04 00:00:00.",
                      "2010-12-22T172201", "2010-Dec-22 17:22:01"])
        {
            assertThrown!DateTimeException(SysTime.fromISOString(str), format("[%s]", str));
        }

        static void test(string str, SysTime st, size_t line = __LINE__)
        {
            if(SysTime.fromISOString(str) != st)
                throw new AssertError("unittest failure", __FILE__, line);
        }

        test("20101222T172201", SysTime(DateTime(2010, 12, 22, 17, 22, 01)));
        test("19990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test("-19990706T123033", SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        test("+019990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test("19990706T123033 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test(" 19990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test(" 19990706T123033 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));

        test("19070707T121212.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
        test("19070707T121212.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
        test("19070707T121212.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1)));
        test("19070707T121212.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
        test("19070707T121212.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
        test("19070707T121212.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
        test("19070707T121212.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));

        auto west60 = new immutable SimpleTimeZone(hours(-1));
        auto west90 = new immutable SimpleTimeZone(minutes(-90));
        auto west480 = new immutable SimpleTimeZone(hours(-8));
        auto east60 = new immutable SimpleTimeZone(hours(1));
        auto east90 = new immutable SimpleTimeZone(minutes(90));
        auto east480 = new immutable SimpleTimeZone(hours(8));

        test("20101222T172201Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC()));
        test("20101222T172201-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("20101222T172201-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("20101222T172201-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
        test("20101222T172201-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
        test("20101222T172201+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("20101222T172201+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("20101222T172201+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("20101222T172201+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));

        test("20101103T065106.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC()));
        test("20101222T172201.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC()));
        test("20101222T172201.23112-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
        test("20101222T172201.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60));
        test("20101222T172201.1-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
        test("20101222T172201.55-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
        test("20101222T172201.1234567+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
        test("20101222T172201.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("20101222T172201.0000000+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("20101222T172201.45+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));

        // @@@DEPRECATED_2017-07@@@
        // This isn't deprecated per se, but that text will make it so that it
        // pops up when deprecations are moved along around July 2017. At that
        // time, the notice on the documentation should be removed, and we may
        // or may not change the behavior of fromISOString to no longer accept
        // ISO extended time zones (the concern being that programs will have
        // written out strings somewhere to read in again that they'll still be
        // reading in for years to come and may not be able to fix, even if the
        // code is fixed). If/when we do change the behavior, these tests will
        // start failing and will need to be updated accordingly.
        test("20101222T172201-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("20101222T172201-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
        test("20101222T172201-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
        test("20101222T172201+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("20101222T172201+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("20101222T172201+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));

        test("20101222T172201.23112-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
        test("20101222T172201.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
        test("20101222T172201.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
        test("20101222T172201.1234567+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
        test("20101222T172201.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("20101222T172201.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
    }


    /++
        Creates a $(LREF SysTime) from a string with the format
        YYYY-MM-DDTHH:MM:SS.FFFFFFFTZ (where F is fractional seconds is the
        time zone). Whitespace is stripped from the given string.

        The exact format is exactly as described in $(D toISOExtString)
        except that trailing zeroes are permitted - including having fractional
        seconds with all zeroes. However, a decimal point with nothing following
        it is invalid.

        If there is no time zone in the string, then $(LREF LocalTime) is used.
        If the time zone is "Z", then $(D UTC) is used. Otherwise, a
        $(LREF SimpleTimeZone) which corresponds to the given offset from UTC is
        used. To get the returned $(LREF SysTime) to be a particular time
        zone, pass in that time zone and the $(LREF SysTime) to be returned
        will be converted to that time zone (though it will still be read in as
        whatever time zone is in its string).

        The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
        -HH:MM.

        Params:
            isoExtString = A string formatted in the ISO Extended format for
                           dates and times.
            tz           = The time zone to convert the given time to (no
                           conversion occurs if null).

        Throws:
            $(LREF DateTimeException) if the given string is not in the ISO
            format or if the resulting $(LREF SysTime) would not be valid.
      +/
    static SysTime fromISOExtString(S)(in S isoExtString, immutable TimeZone tz = null) @safe
        if(isSomeString!(S))
    {
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : countUntil, find;
        import std.format : format;

        auto dstr = to!dstring(strip(isoExtString));

        auto tIndex = dstr.countUntil('T');
        enforce(tIndex != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        auto found = dstr[tIndex + 1 .. $].find('.', 'Z', '+', '-');
        auto dateTimeStr = dstr[0 .. $ - found[0].length];

        dstring fracSecStr;
        dstring zoneStr;

        if(found[1] != 0)
        {
            if(found[1] == 1)
            {
                auto foundTZ = found[0].find('Z', '+', '-');

                if(foundTZ[1] != 0)
                {
                    fracSecStr = found[0][0 .. $ - foundTZ[0].length];
                    zoneStr = foundTZ[0];
                }
                else
                    fracSecStr = found[0];
            }
            else
                zoneStr = found[0];
        }

        try
        {
            auto dateTime = DateTime.fromISOExtString(dateTimeStr);
            auto fracSec = fracSecsFromISOString(fracSecStr);
            Rebindable!(immutable TimeZone) parsedZone;

            if(zoneStr.empty)
                parsedZone = LocalTime();
            else if(zoneStr == "Z")
                parsedZone = UTC();
            else
                parsedZone = SimpleTimeZone.fromISOExtString(zoneStr);

            auto retval = SysTime(dateTime, fracSec, parsedZone);

            if(tz !is null)
                retval.timezone = tz;

            return retval;
        }
        catch(DateTimeException dte)
            throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString));
    }

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

        assert(SysTime.fromISOExtString("1998-12-25T02:15:00.007") ==
               SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7)));

        assert(SysTime.fromISOExtString("0000-01-05T23:09:59.00002") ==
               SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20)));

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

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

        assert(SysTime.fromISOExtString("2010-07-04T07:06:12Z") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC()));

        assert(SysTime.fromISOExtString("2010-07-04T07:06:12-08:00") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
                       new immutable SimpleTimeZone(hours(-8))));
        assert(SysTime.fromISOExtString("2010-07-04T07:06:12+08:00") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
                       new immutable SimpleTimeZone(hours(8))));
    }

    unittest
    {
        import std.format : format;

        foreach(str; ["", "20100704000000", "20100704 000000",
                      "20100704t000000", "20100704T000000.", "20100704T000000.0",
                      "2010-07:0400:00:00", "2010-07-04 00:00:00",
                      "2010-07-04 00:00:00", "2010-07-04t00:00:00",
                      "2010-07-04T00:00:00.", "2010-07-04T00:00:00.A", "2010-07-04T00:00:00.Z",
                      "2010-07-04T00:00:00.00000000", "2010-07-04T00:00:00.00000000",
                      "2010-07-04T00:00:00+", "2010-07-04T00:00:00-",
                      "2010-07-04T00:00:00:", "2010-07-04T00:00:00-:", "2010-07-04T00:00:00+:",
                      "2010-07-04T00:00:00-1:", "2010-07-04T00:00:00+1:", "2010-07-04T00:00:00+1:0",
                      "2010-07-04T00:00:00-12.00", "2010-07-04T00:00:00+12.00",
                      "2010-07-04T00:00:00-8", "2010-07-04T00:00:00+8",
                      "20100704T000000-800", "20100704T000000+800",
                      "20100704T000000-080", "20100704T000000+080",
                      "20100704T000000-2400", "20100704T000000+2400",
                      "20100704T000000-1260", "20100704T000000+1260",
                      "20100704T000000.0-800", "20100704T000000.0+800",
                      "20100704T000000.0-8", "20100704T000000.0+8",
                      "20100704T000000.0-080", "20100704T000000.0+080",
                      "20100704T000000.0-2400", "20100704T000000.0+2400",
                      "20100704T000000.0-1260", "20100704T000000.0+1260",
                      "2010-07-04T00:00:00-8:00", "2010-07-04T00:00:00+8:00",
                      "2010-07-04T00:00:00-24:00", "2010-07-04T00:00:00+24:00",
                      "2010-07-04T00:00:00-12:60", "2010-07-04T00:00:00+12:60",
                      "2010-07-04T00:00:00.0-8:00", "2010-07-04T00:00:00.0+8:00",
                      "2010-07-04T00:00:00.0-8", "2010-07-04T00:00:00.0+8",
                      "2010-07-04T00:00:00.0-24:00", "2010-07-04T00:00:00.0+24:00",
                      "2010-07-04T00:00:00.0-12:60", "2010-07-04T00:00:00.0+12:60",
                      "2010-Jul-0400:00:00", "2010-Jul-04t00:00:00",
                      "2010-Jul-04 00:00:00.", "2010-Jul-04 00:00:00.0",
                      "20101222T172201", "2010-Dec-22 17:22:01"])
        {
            assertThrown!DateTimeException(SysTime.fromISOExtString(str), format("[%s]", str));
        }

        static void test(string str, SysTime st, size_t line = __LINE__)
        {
            if(SysTime.fromISOExtString(str) != st)
                throw new AssertError("unittest failure", __FILE__, line);
        }

        test("2010-12-22T17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01)));
        test("1999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test("-1999-07-06T12:30:33", SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        test("+01999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test("1999-07-06T12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test(" 1999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test(" 1999-07-06T12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));

        test("1907-07-07T12:12:12.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
        test("1907-07-07T12:12:12.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
        test("1907-07-07T12:12:12.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1)));
        test("1907-07-07T12:12:12.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
        test("1907-07-07T12:12:12.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
        test("1907-07-07T12:12:12.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
        test("1907-07-07T12:12:12.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));

        auto west60 = new immutable SimpleTimeZone(hours(-1));
        auto west90 = new immutable SimpleTimeZone(minutes(-90));
        auto west480 = new immutable SimpleTimeZone(hours(-8));
        auto east60 = new immutable SimpleTimeZone(hours(1));
        auto east90 = new immutable SimpleTimeZone(minutes(90));
        auto east480 = new immutable SimpleTimeZone(hours(8));

        test("2010-12-22T17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC()));
        test("2010-12-22T17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("2010-12-22T17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("2010-12-22T17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
        test("2010-12-22T17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
        test("2010-12-22T17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("2010-12-22T17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("2010-12-22T17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("2010-12-22T17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));

        test("2010-11-03T06:51:06.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC()));
        test("2010-12-22T17:22:01.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC()));
        test("2010-12-22T17:22:01.23112-01:00",
             SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
        test("2010-12-22T17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60));
        test("2010-12-22T17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
        test("2010-12-22T17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
        test("2010-12-22T17:22:01.1234567+01:00",
             SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
        test("2010-12-22T17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("2010-12-22T17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("2010-12-22T17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
    }


    /++
        Creates a $(LREF SysTime) from a string with the format
        YYYY-MM-DD HH:MM:SS.FFFFFFFTZ (where F is fractional seconds is the
        time zone). Whitespace is stripped from the given string.

        The exact format is exactly as described in $(D toSimpleString) except
        that trailing zeroes are permitted - including having fractional seconds
        with all zeroes. However, a decimal point with nothing following it is
        invalid.

        If there is no time zone in the string, then $(LREF LocalTime) is used. If
        the time zone is "Z", then $(D UTC) is used. Otherwise, a
        $(LREF SimpleTimeZone) which corresponds to the given offset from UTC is
        used. To get the returned $(LREF SysTime) to be a particular time
        zone, pass in that time zone and the $(LREF SysTime) to be returned
        will be converted to that time zone (though it will still be read in as
        whatever time zone is in its string).

        The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
        -HH:MM.

        Params:
            simpleString = A string formatted in the way that
                           $(D toSimpleString) formats dates and times.
            tz           = The time zone to convert the given time to (no
                           conversion occurs if null).

        Throws:
            $(LREF DateTimeException) if the given string is not in the ISO format
            or if the resulting $(LREF SysTime) would not be valid.
      +/
    static SysTime fromSimpleString(S)(in S simpleString, immutable TimeZone tz = null) @safe
        if(isSomeString!(S))
    {
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : countUntil, find;
        import std.format : format;

        auto dstr = to!dstring(strip(simpleString));

        auto spaceIndex = dstr.countUntil(' ');
        enforce(spaceIndex != -1, new DateTimeException(format("Invalid Simple String: %s", simpleString)));

        auto found = dstr[spaceIndex + 1 .. $].find('.', 'Z', '+', '-');
        auto dateTimeStr = dstr[0 .. $ - found[0].length];

        dstring fracSecStr;
        dstring zoneStr;

        if(found[1] != 0)
        {
            if(found[1] == 1)
            {
                auto foundTZ = found[0].find('Z', '+', '-');

                if(foundTZ[1] != 0)
                {
                    fracSecStr = found[0][0 .. $ - foundTZ[0].length];
                    zoneStr = foundTZ[0];
                }
                else
                    fracSecStr = found[0];
            }
            else
                zoneStr = found[0];
        }

        try
        {
            auto dateTime = DateTime.fromSimpleString(dateTimeStr);
            auto fracSec = fracSecsFromISOString(fracSecStr);
            Rebindable!(immutable TimeZone) parsedZone;

            if(zoneStr.empty)
                parsedZone = LocalTime();
            else if(zoneStr == "Z")
                parsedZone = UTC();
            else
                parsedZone = SimpleTimeZone.fromISOExtString(zoneStr);

            auto retval = SysTime(dateTime, fracSec, parsedZone);

            if(tz !is null)
                retval.timezone = tz;

            return retval;
        }
        catch(DateTimeException dte)
            throw new DateTimeException(format("Invalid Simple String: %s", simpleString));
    }

    ///
    unittest
    {
        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));

        assert(SysTime.fromSimpleString("1998-Dec-25 02:15:00.007") ==
               SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7)));

        assert(SysTime.fromSimpleString("0000-Jan-05 23:09:59.00002") ==
               SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20)));

        assert(SysTime.fromSimpleString("-0004-Jan-05 00:00:02") ==
               SysTime(DateTime(-4, 1, 5, 0, 0, 2)));

        assert(SysTime.fromSimpleString(" 2010-Jul-04 07:06:12 ") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12)));

        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12Z") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC()));

        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12-08:00") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
                       new immutable SimpleTimeZone(hours(-8))));

        assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12+08:00") ==
               SysTime(DateTime(2010, 7, 4, 7, 6, 12),
                       new immutable SimpleTimeZone(hours(8))));
    }

    unittest
    {
        import std.format : format;

        foreach(str; ["", "20100704000000", "20100704 000000",
                      "20100704t000000", "20100704T000000.", "20100704T000000.0",
                      "2010-07-0400:00:00", "2010-07-04 00:00:00", "2010-07-04t00:00:00",
                      "2010-07-04T00:00:00.", "2010-07-04T00:00:00.0",
                      "2010-Jul-0400:00:00", "2010-Jul-04t00:00:00", "2010-Jul-04T00:00:00",
                      "2010-Jul-04 00:00:00.", "2010-Jul-04 00:00:00.A", "2010-Jul-04 00:00:00.Z",
                      "2010-Jul-04 00:00:00.00000000", "2010-Jul-04 00:00:00.00000000",
                      "2010-Jul-04 00:00:00+", "2010-Jul-04 00:00:00-",
                      "2010-Jul-04 00:00:00:", "2010-Jul-04 00:00:00-:",
                      "2010-Jul-04 00:00:00+:", "2010-Jul-04 00:00:00-1:",
                      "2010-Jul-04 00:00:00+1:", "2010-Jul-04 00:00:00+1:0",
                      "2010-Jul-04 00:00:00-12.00", "2010-Jul-04 00:00:00+12.00",
                      "2010-Jul-04 00:00:00-8", "2010-Jul-04 00:00:00+8",
                      "20100704T000000-800", "20100704T000000+800",
                      "20100704T000000-080", "20100704T000000+080",
                      "20100704T000000-2400", "20100704T000000+2400",
                      "20100704T000000-1260", "20100704T000000+1260",
                      "20100704T000000.0-800", "20100704T000000.0+800",
                      "20100704T000000.0-8", "20100704T000000.0+8",
                      "20100704T000000.0-080", "20100704T000000.0+080",
                      "20100704T000000.0-2400", "20100704T000000.0+2400",
                      "20100704T000000.0-1260", "20100704T000000.0+1260",
                      "2010-Jul-04 00:00:00-8:00", "2010-Jul-04 00:00:00+8:00",
                      "2010-Jul-04 00:00:00-08:0", "2010-Jul-04 00:00:00+08:0",
                      "2010-Jul-04 00:00:00-24:00", "2010-Jul-04 00:00:00+24:00",
                      "2010-Jul-04 00:00:00-12:60", "2010-Jul-04 00:00:00+24:60",
                      "2010-Jul-04 00:00:00.0-8:00", "2010-Jul-04 00:00:00+8:00",
                      "2010-Jul-04 00:00:00.0-8", "2010-Jul-04 00:00:00.0+8",
                      "2010-Jul-04 00:00:00.0-08:0", "2010-Jul-04 00:00:00.0+08:0",
                      "2010-Jul-04 00:00:00.0-24:00", "2010-Jul-04 00:00:00.0+24:00",
                      "2010-Jul-04 00:00:00.0-12:60", "2010-Jul-04 00:00:00.0+24:60",
                      "20101222T172201", "2010-12-22T172201"])
        {
            assertThrown!DateTimeException(SysTime.fromSimpleString(str), format("[%s]", str));
        }

        static void test(string str, SysTime st, size_t line = __LINE__)
        {
            if(SysTime.fromSimpleString(str) != st)
                throw new AssertError("unittest failure", __FILE__, line);
        }

        test("2010-Dec-22 17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01)));
        test("1999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test("-1999-Jul-06 12:30:33", SysTime(DateTime(-1999, 7, 6, 12, 30, 33)));
        test("+01999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test("1999-Jul-06 12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test(" 1999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));
        test(" 1999-Jul-06 12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33)));

        test("1907-Jul-07 12:12:12.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
        test("1907-Jul-07 12:12:12.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12)));
        test("1907-Jul-07 12:12:12.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1)));
        test("1907-Jul-07 12:12:12.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
        test("1907-Jul-07 12:12:12.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1)));
        test("1907-Jul-07 12:12:12.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));
        test("1907-Jul-07 12:12:12.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1)));

        auto west60 = new immutable SimpleTimeZone(hours(-1));
        auto west90 = new immutable SimpleTimeZone(minutes(-90));
        auto west480 = new immutable SimpleTimeZone(hours(-8));
        auto east60 = new immutable SimpleTimeZone(hours(1));
        auto east90 = new immutable SimpleTimeZone(minutes(90));
        auto east480 = new immutable SimpleTimeZone(hours(8));

        test("2010-Dec-22 17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC()));
        test("2010-Dec-22 17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("2010-Dec-22 17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60));
        test("2010-Dec-22 17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90));
        test("2010-Dec-22 17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480));
        test("2010-Dec-22 17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("2010-Dec-22 17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("2010-Dec-22 17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("2010-Dec-22 17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480));

        test("2010-Nov-03 06:51:06.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC()));
        test("2010-Dec-22 17:22:01.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC()));
        test("2010-Dec-22 17:22:01.23112-01:00",
             SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60));
        test("2010-Dec-22 17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60));
        test("2010-Dec-22 17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90));
        test("2010-Dec-22 17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480));
        test("2010-Dec-22 17:22:01.1234567+01:00",
            SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60));
        test("2010-Dec-22 17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60));
        test("2010-Dec-22 17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90));
        test("2010-Dec-22 17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480));
    }


    /++
        Returns the $(LREF SysTime) farthest in the past which is representable
        by $(LREF SysTime).

        The $(LREF SysTime) which is returned is in UTC.
      +/
    @property static SysTime min() @safe pure nothrow
    {
        return SysTime(long.min, UTC());
    }

    unittest
    {
        assert(SysTime.min.year < 0);
        assert(SysTime.min < SysTime.max);
    }


    /++
        Returns the $(LREF SysTime) farthest in the future which is representable
        by $(LREF SysTime).

        The $(LREF SysTime) which is returned is in UTC.
      +/
    @property static SysTime max() @safe pure nothrow
    {
        return SysTime(long.max, UTC());
    }

    unittest
    {
        assert(SysTime.max.year > 0);
        assert(SysTime.max > SysTime.min);
    }


private:

    /+
        Returns $(D stdTime) converted to $(LREF SysTime)'s time zone.
      +/
    @property long adjTime() @safe const nothrow
    {
        return _timezone.utcToTZ(_stdTime);
    }


    /+
        Converts the given hnsecs from $(LREF SysTime)'s time zone to std time.
      +/
    @property void adjTime(long adjTime) @safe nothrow
    {
        _stdTime = _timezone.tzToUTC(adjTime);
    }


    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5058
    /+
    invariant()
    {
        assert(_timezone !is null, "Invariant Failure: timezone is null. Were you foolish enough to use SysTime.init? (since timezone for SysTime.init can't be set at compile time).");
    }
    +/


    long  _stdTime;
    Rebindable!(immutable TimeZone) _timezone;
}


/++
    Represents a date in the
    $(WEB 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 $(D Date) is
    optimized for calendar-based operations.

    $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian
    leap year calculations for its entire length. As per
    $(WEB 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:
            $(LREF DateTimeException) 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.
            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;
    }

    unittest
    {
        assert(Date(1, 1, 1) == Date.init);

        static void testDate(in 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
    {
        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;

                try
                    dayOfYear = day;
                catch(Exception e)
                    assert(0, "dayOfYear assignment threw.");
            }
        }
        else if(day <= 0 && -day < daysInLeapYear)
        {
            _year = 0;

            try
                dayOfYear = (daysInLeapYear + day);
            catch(Exception e)
                assert(0, "dayOfYear assignment threw.");
        }
        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;

                try
                    dayOfYear = newDoY;
                catch(Exception e)
                    assert(0, "dayOfYear assignment threw.");
            }
        }
    }

    unittest
    {
        import std.range;

        //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(in Date rhs) @safe const pure nothrow
    {
        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;
    }

    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() @safe const pure nothrow
    {
        return _year;
    }

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

    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:
            $(LREF DateTimeException) 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;
    }

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

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

        static void testDate(Date date, int year, in 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:
            $(LREF DateTimeException) if $(D isAD) is true.
     +/
    @property ushort yearBC() @safe const pure
    {
        import std.format : format;

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

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

    unittest
    {
        assertThrown!DateTimeException((in 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:
            $(LREF DateTimeException) 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);
    }

    ///
    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));
    }

    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() @safe const pure nothrow
    {
        return _month;
    }

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

    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:
            $(LREF DateTimeException) 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;
    }

    unittest
    {
        static void testDate(Date date, Month month, in 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() @safe const pure nothrow
    {
        return _day;
    }

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

    unittest
    {
        import std.range;
        import std.format : format;

        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:
            $(LREF DateTimeException) 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;
    }

    unittest
    {
        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.
      +/
    ref Date add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
        if(units == "years")
    {
        immutable newYear = _year + value;

        _year += value;

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

        return this;
    }

    ///
    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 AllowDayOverlow.yes
    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 AllowDayOverlow.no
    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.
    ref Date add(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
        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 AllowDayOverlow.yes
    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 AllowDayOverlow.no
    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.
      +/
    ref Date roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
        if(units == "years")
    {
        return add!"years"(value, allowOverflow);
    }

    ///
    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));
    }

    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.
    ref Date roll(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow
        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 AllowDayOverlow.yes
    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 AllowDayOverlow.no
    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 $(D "days").

        Params:
            units = The units to add. Must be $(D "days").
            days  = The number of days to add to this $(LREF Date).
      +/
    ref Date roll(string units)(long days) @safe pure nothrow
        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;
    }

    ///
    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));
    }

    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)));
    }


    /++
        Gives the result of adding or subtracting a $(CXREF tiem, Duration) from
        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 $(CXREF time, Duration) to add to or subtract from
                       this $(LREF Date).
      +/
    Date opBinary(string op)(Duration duration) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        Date retval = this;
        immutable days = duration.total!"days";
        mixin("return retval._addDays(" ~ op ~ "days);");
    }

    ///
    unittest
    {
        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));
    }

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

        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));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines + and - with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    Date opBinary(string op)(TickDuration td) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        Date retval = this;
        immutable days = convert!("hnsecs", "days")(td.hnsecs);
        mixin("return retval._addDays(" ~ op ~ "days);");
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            auto date = Date(1999, 7, 6);

            assert(date + TickDuration.from!"usecs"(86_400_000_000) == Date(1999, 7, 7));
            assert(date + TickDuration.from!"usecs"(-86_400_000_000) == Date(1999, 7, 5));

            assert(date - TickDuration.from!"usecs"(-86_400_000_000) == Date(1999, 7, 7));
            assert(date - TickDuration.from!"usecs"(86_400_000_000) == Date(1999, 7, 5));
        }
    }


    /++
        Gives the result of adding or subtracting a $(CXREF time, Duration) 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 $(CXREF time, Duration) to add to or subtract from
                       this $(LREF Date).
      +/
    ref Date opOpAssign(string op)(Duration duration) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        immutable days = duration.total!"days";
        mixin("return _addDays(" ~ op ~ "days);");
    }

    unittest
    {
        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));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines += and -= with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    ref Date opOpAssign(string op)(TickDuration td) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        immutable days = convert!("seconds", "days")(td.seconds);
        mixin("return _addDays(" ~ op ~ "days);");
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            {
                auto date = Date(1999, 7, 6);
                date += TickDuration.from!"usecs"(86_400_000_000);
                assert(date == Date(1999, 7, 7));
            }

            {
                auto date = Date(1999, 7, 6);
                date += TickDuration.from!"usecs"(-86_400_000_000);
                assert(date == Date(1999, 7, 5));
            }

            {
                auto date = Date(1999, 7, 6);
                date -= TickDuration.from!"usecs"(-86_400_000_000);
                assert(date == Date(1999, 7, 7));
            }

            {
                auto date = Date(1999, 7, 6);
                date -= TickDuration.from!"usecs"(86_400_000_000);
                assert(date == Date(1999, 7, 5));
            }
        }
    }


    /++
        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)(in Date rhs) @safe const pure nothrow
        if(op == "-")
    {
        return dur!"days"(this.dayOfGregorianCal - rhs.dayOfGregorianCal);
    }

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

        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 SysTime)s. To get the difference in days or weeks,
        subtract the $(LREF SysTime)s themselves and use the $(CXREF time, Duration)
        that results. Because converting between months and smaller
        units requires a specific date (which $(CXREF time, Duration)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(in Date rhs) @safe const pure nothrow
    {
        immutable yearDiff = _year - rhs._year;
        immutable monthDiff = _month - rhs._month;

        return yearDiff * 12 + monthDiff;
    }

    ///
    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);
    }

    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() @safe const pure nothrow
    {
        return yearIsLeapYear(_year);
    }

    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() @safe const pure nothrow
    {
        return getDayOfWeek(dayOfGregorianCal);
    }

    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() @safe const pure nothrow
    {
        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.");
    }

    ///
    unittest
    {
        assert(Date(1999, 1, 1).dayOfYear == 1);
        assert(Date(1999, 12, 31).dayOfYear == 365);
        assert(Date(2000, 12, 31).dayOfYear == 366);
    }

    unittest
    {
        import std.range;

        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:
            $(LREF DateTimeException) if the given day is an invalid day of the
            year.
      +/
    @property void dayOfYear(int day) @safe pure
    {
        immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap;

        if(day <= 0 || day > (isLeapYear ? daysInLeapYear : daysInYear) )
            throw new DateTimeException("Invalid day of the year.");

        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.");
    }

    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() @safe const pure nothrow
    {
        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;
        }
    }

    ///
    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);
    }

    unittest
    {
        import std.range;

        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
    {
        this = Date(day);
    }

    ///
    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));
    }

    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:
            $(WEB en.wikipedia.org/wiki/ISO_week_date, ISO Week Date)
      +/
    @property ubyte isoWeek() @safe const 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.");
    }

    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() @safe const pure nothrow
    {
        try
            return Date(_year, _month, maxDay(_year, _month));
        catch(Exception e)
            assert(0, "Date's constructor threw.");
    }

    ///
    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));
    }

    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() @safe const pure nothrow
    {
        return maxDay(_year, _month);
    }

    ///
    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);
    }

    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() @safe const pure nothrow
    {
        return _year > 0;
    }

    ///
    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);
    }

    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 $(WEB en.wikipedia.org/wiki/Julian_day, Julian day) for this $(LREF Date) at noon (since the Julian day changes
        at noon).
      +/
    @property long julianDay() @safe const pure nothrow
    {
        return dayOfGregorianCal + 1_721_425;
    }

    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 $(WEB 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() @safe const pure nothrow
    {
        return julianDay - 2_400_001;
    }

    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.
      +/
    string toISOString() @safe const pure nothrow
    {
        import std.format : format;
        try
        {
            if(_year >= 0)
            {
                if(_year < 10_000)
                    return format("%04d%02d%02d", _year, _month, _day);
                else
                    return format("+%05d%02d%02d", _year, _month, _day);
            }
            else if(_year > -10_000)
                return format("%05d%02d%02d", _year, _month, _day);
            else
                return format("%06d%02d%02d", _year, _month, _day);
        }
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    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");
    }

    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");
    }

    /++
        Converts this $(LREF Date) to a string with the format YYYY-MM-DD.
      +/
    string toISOExtString() @safe const pure nothrow
    {
        import std.format : format;
        try
        {
            if(_year >= 0)
            {
                if(_year < 10_000)
                    return format("%04d-%02d-%02d", _year, _month, _day);
                else
                    return format("+%05d-%02d-%02d", _year, _month, _day);
            }
            else if(_year > -10_000)
                return format("%05d-%02d-%02d", _year, _month, _day);
            else
                return format("%06d-%02d-%02d", _year, _month, _day);
        }
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    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");
    }

    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");
    }

    /++
        Converts this $(LREF Date) to a string with the format YYYY-Mon-DD.
      +/
    string toSimpleString() @safe const pure nothrow
    {
        import std.format : format;
        try
        {
            if(_year >= 0)
            {
                if(_year < 10_000)
                    return format("%04d-%s-%02d", _year, monthToString(_month), _day);
                else
                    return format("+%05d-%s-%02d", _year, monthToString(_month), _day);
            }
            else if(_year > -10_000)
                return format("%05d-%s-%02d", _year, monthToString(_month), _day);
            else
                return format("%06d-%s-%02d", _year, monthToString(_month), _day);
        }
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    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");
    }

    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");
    }


    /++
        Converts this $(LREF Date) to a string.
      +/
    string toString() @safe const pure nothrow
    {
        return toSimpleString();
    }

    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());
    }


    /++
        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:
            $(LREF DateTimeException) if the given string is not in the ISO format
            or if the resulting $(LREF Date) would not be valid.
      +/
    static Date fromISOString(S)(in S isoString) @safe pure
        if(isSomeString!S)
    {
        import std.ascii : isDigit;
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : all, startsWith;
        import std.format : format;

        auto dstr = to!dstring(strip(isoString));

        enforce(dstr.length >= 8, new DateTimeException(format("Invalid ISO String: %s", isoString)));

        auto day = dstr[$-2 .. $];
        auto month = dstr[$-4 .. $-2];
        auto year = dstr[0 .. $-4];

        enforce(all!isDigit(day), new DateTimeException(format("Invalid ISO String: %s", isoString)));
        enforce(all!isDigit(month), new DateTimeException(format("Invalid ISO String: %s", isoString)));

        if(year.length > 4)
        {
            enforce(year.startsWith('-', '+'),
                    new DateTimeException(format("Invalid ISO String: %s", isoString)));
            enforce(all!isDigit(year[1..$]),
                    new DateTimeException(format("Invalid ISO String: %s", isoString)));
        }
        else
            enforce(all!isDigit(year), new DateTimeException(format("Invalid ISO String: %s", isoString)));

        return Date(to!short(year), to!ubyte(month), to!ubyte(day));
    }

    ///
    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));
    }

    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));
    }


    /++
        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:
            $(LREF DateTimeException) 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)(in S isoExtString) @safe pure
        if(isSomeString!(S))
    {
        import std.ascii : isDigit;
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : all, startsWith;
        import std.format : format;

        auto dstr = to!dstring(strip(isoExtString));

        enforce(dstr.length >= 10, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        auto day = dstr[$-2 .. $];
        auto month = dstr[$-5 .. $-3];
        auto year = dstr[0 .. $-6];

        enforce(dstr[$-3] == '-', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(dstr[$-6] == '-', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(all!isDigit(day),
                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(all!isDigit(month),
                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        if(year.length > 4)
        {
            enforce(year.startsWith('-', '+'),
                    new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
            enforce(all!isDigit(year[1..$]),
                    new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        }
        else
            enforce(all!isDigit(year),
                    new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        return Date(to!short(year), to!ubyte(month), to!ubyte(day));
    }

    ///
    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));
    }

    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));
    }


    /++
        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:
            $(LREF DateTimeException) if the given string is not in the correct
            format or if the resulting $(LREF Date) would not be valid.
      +/
    static Date fromSimpleString(S)(in S simpleString) @safe pure
        if(isSomeString!(S))
    {
        import std.ascii : isDigit;
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : all, startsWith;
        import std.format : format;

        auto dstr = to!dstring(strip(simpleString));

        enforce(dstr.length >= 11, new DateTimeException(format("Invalid string format: %s", simpleString)));

        auto day = dstr[$-2 .. $];
        auto month = monthFromString(to!string(dstr[$-6 .. $-3]));
        auto year = dstr[0 .. $-7];

        enforce(dstr[$-3] == '-', new DateTimeException(format("Invalid string format: %s", simpleString)));
        enforce(dstr[$-7] == '-', new DateTimeException(format("Invalid string format: %s", simpleString)));
        enforce(all!isDigit(day), new DateTimeException(format("Invalid string format: %s", simpleString)));

        if(year.length > 4)
        {
            enforce(year.startsWith('-', '+'),
                    new DateTimeException(format("Invalid string format: %s", simpleString)));
            enforce(all!isDigit(year[1..$]),
                    new DateTimeException(format("Invalid string format: %s", simpleString)));
        }
        else
            enforce(all!isDigit(year),
                    new DateTimeException(format("Invalid string format: %s", simpleString)));

        return Date(to!short(year), month, to!ubyte(day));
    }

    ///
    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));
    }

    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));
    }


    /++
        Returns the $(LREF Date) farthest in the past which is representable by
        $(LREF Date).
      +/
    @property static Date min() @safe pure nothrow
    {
        auto date = Date.init;
        date._year = short.min;
        date._month = Month.jan;
        date._day = 1;

        return date;
    }

    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
    {
        auto date = Date.init;
        date._year = short.max;
        date._month = Month.dec;
        date._day = 31;

        return date;
    }

    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
    {
        if(!valid!"months"(month))
            return false;

        return valid!"days"(year, month, day);
    }

    /+
        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.

        $(D _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
    {
        dayOfGregorianCal = cast(int)(dayOfGregorianCal + days);
        return this;
    }

    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;
}



/++
    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:
            $(LREF DateTimeException) 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;
    }

    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(in TimeOfDay rhs) @safe const pure nothrow
    {
        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;
    }

    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() @safe const pure nothrow
    {
        return _hour;
    }

    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:
            $(LREF DateTimeException) 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;
    }

    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() @safe const pure nothrow
    {
        return _minute;
    }

    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:
            $(LREF DateTimeException) 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;
    }

    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() @safe const pure nothrow
    {
        return _second;
    }

    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:
            $(LREF DateTimeException) 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;
    }

    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 $(D "hours"), $(D "minutes"), and $(D "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
        if(units == "hours")
    {
        return this += dur!"hours"(value);
    }

    ///
    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));
    }

    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)));
    }


    //Shares documentation with "hours" version.
    ref TimeOfDay roll(string units)(long value) @safe pure nothrow
        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"().
    unittest
    {
        static void testTOD(TimeOfDay orig, int minutes, in 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"().
    unittest
    {
        static void testTOD(TimeOfDay orig, int seconds, in 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)));
    }


    /++
        Gives the result of adding or subtracting a $(CXREF time, Duration) 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 $(CXREF time, Duration) to add to or subtract from
                       this $(LREF TimeOfDay).
      +/
    TimeOfDay opBinary(string op)(Duration duration) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        TimeOfDay retval = this;
        immutable seconds = duration.total!"seconds";
        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
    }

    ///
    unittest
    {
        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));
    }

    unittest
    {
        auto tod = TimeOfDay(12, 30, 33);

        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));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines + and - with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    TimeOfDay opBinary(string op)(TickDuration td) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        TimeOfDay retval = this;
        immutable seconds = td.seconds;
        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            auto tod = TimeOfDay(12, 30, 33);

            assert(tod + TickDuration.from!"usecs"(7_000_000) == TimeOfDay(12, 30, 40));
            assert(tod + TickDuration.from!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26));

            assert(tod - TickDuration.from!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40));
            assert(tod - TickDuration.from!"usecs"(7_000_000) == TimeOfDay(12, 30, 26));
        }
    }


    /++
        Gives the result of adding or subtracting a $(CXREF time, Duration) 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 $(CXREF time, Duration) to add to or subtract from
                       this $(LREF TimeOfDay).
      +/
    ref TimeOfDay opOpAssign(string op)(Duration duration) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        immutable seconds = duration.total!"seconds";
        mixin("return _addSeconds(" ~ op ~ "seconds);");
    }

    unittest
    {
        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));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines += and -= with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    ref TimeOfDay opOpAssign(string op)(TickDuration td) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        immutable seconds = td.seconds;
        mixin("return _addSeconds(" ~ op ~ "seconds);");
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            {
                auto tod = TimeOfDay(12, 30, 33);
                tod += TickDuration.from!"usecs"(7_000_000);
                assert(tod == TimeOfDay(12, 30, 40));
            }

            {
                auto tod = TimeOfDay(12, 30, 33);
                tod += TickDuration.from!"usecs"(-7_000_000);
                assert(tod == TimeOfDay(12, 30, 26));
            }

            {
                auto tod = TimeOfDay(12, 30, 33);
                tod -= TickDuration.from!"usecs"(-7_000_000);
                assert(tod == TimeOfDay(12, 30, 40));
            }

            {
                auto tod = TimeOfDay(12, 30, 33);
                tod -= TickDuration.from!"usecs"(7_000_000);
                assert(tod == TimeOfDay(12, 30, 26));
            }
        }
    }


    /++
        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)(in TimeOfDay rhs) @safe const pure nothrow
        if(op == "-")
    {
        immutable lhsSec = _hour * 3600 + _minute * 60 + _second;
        immutable rhsSec = rhs._hour * 3600 + rhs._minute * 60 + rhs._second;

        return dur!"seconds"(lhsSec - rhsSec);
    }

    unittest
    {
        auto tod = TimeOfDay(12, 30, 33);

        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.
      +/
    string toISOString() @safe const pure nothrow
    {
        import std.format : format;
        try
            return format("%02d%02d%02d", _hour, _minute, _second);
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    unittest
    {
        assert(TimeOfDay(0, 0, 0).toISOString() == "000000");
        assert(TimeOfDay(12, 30, 33).toISOString() == "123033");
    }

    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.
      +/
    string toISOExtString() @safe const pure nothrow
    {
        import std.format : format;
        try
            return format("%02d:%02d:%02d", _hour, _minute, _second);
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    unittest
    {
        assert(TimeOfDay(0, 0, 0).toISOExtString() == "00:00:00");
        assert(TimeOfDay(12, 30, 33).toISOExtString() == "12:30:33");
    }

    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.
      +/
    string toString() @safe const pure nothrow
    {
        return toISOExtString();
    }

    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:
            $(LREF DateTimeException) if the given string is not in the ISO format
            or if the resulting $(LREF TimeOfDay) would not be valid.
      +/
    static TimeOfDay fromISOString(S)(in S isoString) @safe pure
        if(isSomeString!S)
    {
        import std.ascii : isDigit;
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : all;
        import std.format : format;

        auto dstr = to!dstring(strip(isoString));

        enforce(dstr.length == 6, new DateTimeException(format("Invalid ISO String: %s", isoString)));

        auto hours = dstr[0 .. 2];
        auto minutes = dstr[2 .. 4];
        auto seconds = dstr[4 .. $];

        enforce(all!isDigit(hours), new DateTimeException(format("Invalid ISO String: %s", isoString)));
        enforce(all!isDigit(minutes), new DateTimeException(format("Invalid ISO String: %s", isoString)));
        enforce(all!isDigit(seconds), new DateTimeException(format("Invalid ISO String: %s", isoString)));

        return TimeOfDay(to!int(hours), to!int(minutes), to!int(seconds));
    }

    ///
    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));
    }

    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));
    }


    /++
        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:
            $(LREF DateTimeException) 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)(in S isoExtString) @safe pure
        if(isSomeString!S)
    {
        import std.ascii : isDigit;
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : all;
        import std.format : format;

        auto dstr = to!dstring(strip(isoExtString));

        enforce(dstr.length == 8, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        auto hours = dstr[0 .. 2];
        auto minutes = dstr[3 .. 5];
        auto seconds = dstr[6 .. $];

        enforce(dstr[2] == ':', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(dstr[5] == ':', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(all!isDigit(hours),
                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(all!isDigit(minutes),
                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));
        enforce(all!isDigit(seconds),
                new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)));

        return TimeOfDay(to!int(hours), to!int(minutes), to!int(seconds));
    }

    ///
    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));
    }

    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));
    }


    /++
        Returns midnight.
      +/
    @property static TimeOfDay min() @safe pure nothrow
    {
        return TimeOfDay.init;
    }

    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
    {
        auto tod = TimeOfDay.init;
        tod._hour = maxHour;
        tod._minute = maxMinute;
        tod._second = maxSecond;

        return tod;
    }

    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
    {
        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;
    }

    unittest
    {
        static void testTOD(TimeOfDay orig, int seconds, in 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
    {
        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));
    }

    ubyte _hour;
    ubyte _minute;
    ubyte _second;

    enum ubyte maxHour   = 24 - 1;
    enum ubyte maxMinute = 60 - 1;
    enum ubyte maxSecond = 60 - 1;
}


/++
   Combines the $(LREF Date) and $(LREF TimeOfDay) 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
   $(LREF SysTime). $(LREF SysTime) has a concept of time zone and has much higher
   precision (hnsecs). $(D 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(in Date date, in TimeOfDay tod = TimeOfDay.init) @safe pure nothrow
    {
        _date = date;
        _tod = tod;
    }

    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.
            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);
    }

    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 $(D 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(in DateTime rhs) @safe const pure nothrow
    {
        immutable dateResult = _date.opCmp(rhs._date);

        if(dateResult != 0)
            return dateResult;

        return _tod.opCmp(rhs._tod);
    }

    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() @safe const pure nothrow
    {
        return _date;
    }

    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(in Date date) @safe pure nothrow
    {
        _date = date;
    }

    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() @safe const pure nothrow
    {
        return _tod;
    }

    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 $(LREF TimeOfDay) to set this $(LREF DateTime)'s time portion
                  to.
      +/
    @property void timeOfDay(in TimeOfDay tod) @safe pure nothrow
    {
        _tod = tod;
    }

    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() @safe const pure nothrow
    {
        return _date.year;
    }

    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:
            $(LREF DateTimeException) 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;
    }

    ///
    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);
    }

    unittest
    {
        static void testDT(DateTime dt, int year, in 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:
            $(LREF DateTimeException) if $(D isAD) is true.
     +/
    @property short yearBC() @safe const pure
    {
        return _date.yearBC;
    }

    ///
    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);
    }

    unittest
    {
        assertThrown!DateTimeException((in 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:
            $(LREF DateTimeException) if a non-positive value is given.
     +/
    @property void yearBC(int year) @safe pure
    {
        _date.yearBC = year;
    }

    ///
    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)));
    }

    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() @safe const pure nothrow
    {
        return _date.month;
    }

    ///
    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);
    }

    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:
            $(LREF DateTimeException) if the given month is not a valid month.
     +/
    @property void month(Month month) @safe pure
    {
        _date.month = month;
    }

    unittest
    {
        static void testDT(DateTime dt, Month month, in 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() @safe const pure nothrow
    {
        return _date.day;
    }

    ///
    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);
    }

    unittest
    {
        import std.range;
        import std.format : format;

        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:
            $(LREF DateTimeException) if the given day is not a valid day of the
            current month.
     +/
    @property void day(int day) @safe pure
    {
        _date.day = day;
    }

    unittest
    {
        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() @safe const pure nothrow
    {
        return _tod.hour;
    }

    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:
            $(LREF DateTimeException) if the given hour would result in an invalid
            $(LREF DateTime).
     +/
    @property void hour(int hour) @safe pure
    {
        _tod.hour = hour;
    }

    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() @safe const pure nothrow
    {
        return _tod.minute;
    }

    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:
            $(LREF DateTimeException) if the given minute would result in an
            invalid $(LREF DateTime).
     +/
    @property void minute(int minute) @safe pure
    {
        _tod.minute = minute;
    }

    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() @safe const pure nothrow
    {
        return _tod.second;
    }

    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:
            $(LREF DateTimeException) if the given seconds would result in an
            invalid $(LREF DateTime).
     +/
    @property void second(int second) @safe pure
    {
        _tod.second = second;
    }

    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
        if(units == "years" ||
           units == "months")
    {
        _date.add!units(value, allowOverflow);
        return this;
    }

    ///
    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));
    }

    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
        if(units == "years" ||
           units == "months")
    {
        _date.roll!units(value, allowOverflow);
        return this;
    }

    ///
    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));
    }

    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 $(D "days"), $(D "minutes"), $(D "hours"),
        $(D "minutes"), and $(D "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
        if(units == "days")
    {
        _date.roll!"days"(value);
        return this;
    }

    ///
    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));
    }

    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)));
    }


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

    //Test roll!"hours"().
    unittest
    {
        static void testDT(DateTime orig, int hours, in 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"().
    unittest
    {
        static void testDT(DateTime orig, int minutes, in 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"().
    unittest
    {
        static void testDT(DateTime orig, int seconds, in 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)));
    }


    /++
        Gives the result of adding or subtracting a $(CXREF time, Duration) 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 $(CXREF time, Duration) to add to or subtract from
                       this $(LREF DateTime).
      +/
    DateTime opBinary(string op)(Duration duration) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        DateTime retval = this;
        immutable seconds = duration.total!"seconds";
        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
    }

    ///
    unittest
    {
        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));
    }

    unittest
    {
        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));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines + and - with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    DateTime opBinary(string op)(in TickDuration td) @safe const pure nothrow
        if(op == "+" || op == "-")
    {
        DateTime retval = this;
        immutable seconds = td.seconds;
        mixin("return retval._addSeconds(" ~ op ~ "seconds);");
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));

            assert(dt + TickDuration.from!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
            assert(dt + TickDuration.from!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));

            assert(dt - TickDuration.from!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
            assert(dt - TickDuration.from!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
        }
    }


    /++
        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, D)(in D duration) @safe pure nothrow
        if((op == "+" || op == "-") &&
           (is(Unqual!D == Duration) ||
            is(Unqual!D == TickDuration)))
    {
        import std.format : format;

        DateTime retval = this;

        static if(is(Unqual!D == Duration))
            immutable hnsecs = duration.total!"hnsecs";
        else static if(is(Unqual!D == TickDuration))
            immutable hnsecs = duration.hnsecs;

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

    unittest
    {
        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));
    }

    // @@@DEPRECATED_2017-01@@@
    /++
        $(RED Deprecated. $(CXREF time, TickDuration) is going to be deprecated
              in favor of $(CXREF time, MonoTime) and $(CXREF time, Duration).
              Use $(D Duration) instead. This overload will be removed in
              January 2017.)

        Defines += and -= with $(CXREF time, TickDuration).
      +/
    deprecated("Use Duration instead of TickDuration.")
    ref DateTime opOpAssign(string op)(TickDuration td) @safe pure nothrow
        if(op == "+" || op == "-")
    {
        DateTime retval = this;
        immutable seconds = td.seconds;
        mixin("return _addSeconds(" ~ op ~ "seconds);");
    }

    deprecated unittest
    {
        //This probably only runs in cases where gettimeofday() is used, but it's
        //hard to do this test correctly with variable ticksPerSec.
        if(TickDuration.ticksPerSec == 1_000_000)
        {
            {
                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
                dt += TickDuration.from!"usecs"(7_000_000);
                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
            }

            {
                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
                dt += TickDuration.from!"usecs"(-7_000_000);
                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
            }

            {
                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
                dt -= TickDuration.from!"usecs"(-7_000_000);
                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40)));
            }

            {
                auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33));
                dt -= TickDuration.from!"usecs"(7_000_000);
                assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26)));
            }
        }
    }


    /++
        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)(in DateTime rhs) @safe const pure nothrow
        if(op == "-")
    {
        immutable dateResult = _date - rhs.date;
        immutable todResult = _tod - rhs._tod;

        return dur!"hnsecs"(dateResult.total!"hnsecs" + todResult.total!"hnsecs");
    }

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

        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 SysTime)s. To get the difference in days or weeks,
        subtract the $(LREF SysTime)s themselves and use the $(CXREF time, Duration)
        that results. Because converting between months and smaller
        units requires a specific date (which $(CXREF time, Duration)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(in DateTime rhs) @safe const pure nothrow
    {
        return _date.diffMonths(rhs._date);
    }

    ///
    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);
    }

    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() @safe const pure nothrow
    {
        return _date.isLeapYear;
    }

    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() @safe const pure nothrow
    {
        return _date.dayOfWeek;
    }

    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() @safe const pure nothrow
    {
        return _date.dayOfYear;
    }

    ///
    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);
    }

    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;
    }

    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() @safe const pure nothrow
    {
        return _date.dayOfGregorianCal;
    }

    ///
    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);
    }

    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
    {
        _date.dayOfGregorianCal = days;
    }

    ///
    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)));
    }

    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:
            $(WEB en.wikipedia.org/wiki/ISO_week_date, ISO Week Date)
      +/
    @property ubyte isoWeek() @safe const pure nothrow
    {
        return _date.isoWeek;
    }

    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() @safe const pure nothrow
    {
        try
            return DateTime(_date.endOfMonth, TimeOfDay(23, 59, 59));
        catch(Exception e)
            assert(0, "DateTime constructor threw.");
    }

    ///
    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)));
    }

    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() @safe const pure nothrow
    {
        return _date.daysInMonth;
    }

    ///
    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);
    }

    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() @safe const pure nothrow
    {
        return _date.isAD;
    }

    ///
    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);
    }

    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 $(WEB 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() @safe const pure nothrow
    {
        if(_tod._hour < 12)
            return _date.julianDay - 1;
        else
            return _date.julianDay;
    }

    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 $(WEB 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() @safe const pure nothrow
    {
        return _date.modJulianDay;
    }

    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.
      +/
    string toISOString() @safe const pure nothrow
    {
        import std.format : format;
        try
            return format("%sT%s", _date.toISOString(), _tod.toISOString());
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    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");
    }

    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.
      +/
    string toISOExtString() @safe const pure nothrow
    {
        import std.format : format;
        try
            return format("%sT%s", _date.toISOExtString(), _tod.toISOExtString());
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    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");
    }

    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.
      +/
    string toSimpleString() @safe const pure nothrow
    {
        import std.format : format;
        try
            return format("%s %s", _date.toSimpleString(), _tod.toString());
        catch(Exception e)
            assert(0, "format() threw.");
    }

    ///
    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");
    }

    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.
      +/
    string toString() @safe const pure nothrow
    {
        return toSimpleString();
    }

    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());
    }



    /++
        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:
            $(LREF DateTimeException) if the given string is not in the ISO format
            or if the resulting $(LREF DateTime) would not be valid.
      +/
    static DateTime fromISOString(S)(in S isoString) @safe pure
        if(isSomeString!S)
    {
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : countUntil;
        import std.format : format;

        immutable dstr = to!dstring(strip(isoString));

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

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

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

        return DateTime(date, tod);
    }

    ///
    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)));
    }

    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)));
    }


    /++
        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:
            $(LREF DateTimeException) 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)(in S isoExtString) @safe pure
        if(isSomeString!(S))
    {
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : countUntil;
        import std.format : format;

        immutable dstr = to!dstring(strip(isoExtString));

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

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

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

        return DateTime(date, tod);
    }

    ///
    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)));
    }

    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)));
    }


    /++
        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:
            $(LREF DateTimeException) if the given string is not in the correct
            format or if the resulting $(LREF DateTime) would not be valid.
      +/
    static DateTime fromSimpleString(S)(in S simpleString) @safe pure
        if(isSomeString!(S))
    {
        import std.string : strip;
        import std.conv : to;
        import std.algorithm : countUntil;
        import std.format : format;

        immutable dstr = to!dstring(strip(simpleString));

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

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

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

        return DateTime(date, tod);
    }

    ///
    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)));
    }

    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)));
    }


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

        return dt;
    }

    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
    out(result)
    {
        assert(result._date == Date.max);
        assert(result._tod == TimeOfDay.max);
    }
    body
    {
        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;
    }

    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
    {
        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;
    }

    unittest
    {
        static void testDT(DateTime orig, int seconds, in 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;
}


//==============================================================================
// Section with intervals.
//==============================================================================

/++
    Represents an interval of time.

    An $(D Interval) has a starting point and an end point. The interval of time
    is therefore the time starting at the starting point up to, but not
    including, the end point. e.g.

    $(BOOKTABLE,
    $(TR $(TD [January 5th, 2010 - March 10th, 2010$(RPAREN)))
    $(TR $(TD [05:00:30 - 12:00:00$(RPAREN)))
    $(TR $(TD [1982-01-04T08:59:00 - 2010-07-04T12:00:00$(RPAREN)))
    )

    A range can be obtained from an $(D Interval), allowing iteration over
    that interval, with the exact time points which are iterated over depending
    on the function which generates the range.
  +/
struct Interval(TP)
{
public:

    /++
        Params:
            begin = The time point which begins the interval.
            end   = The time point which ends (but is not included in) the
                    interval.

        Throws:
            $(LREF DateTimeException) if $(D_PARAM end) is before $(D_PARAM begin).

        Example:
--------------------
Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
--------------------
      +/
    this(U)(in TP begin, in U end) pure
        if(is(Unqual!TP == Unqual!U))
    {
        if(!_valid(begin, end))
            throw new DateTimeException("Arguments would result in an invalid Interval.");

        _begin = cast(TP)begin;
        _end = cast(TP)end;
    }


    /++
        Params:
            begin    = The time point which begins the interval.
            duration = The duration from the starting point to the end point.

        Throws:
            $(LREF DateTimeException) if the resulting $(D end) is before
            $(D begin).

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), dur!"days"(3)) ==
       Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5)));
--------------------
      +/
    this(D)(in TP begin, in D duration) pure
        if(__traits(compiles, begin + duration))
    {
        _begin = cast(TP)begin;
        _end = begin + duration;

        if(!_valid(_begin, _end))
            throw new DateTimeException("Arguments would result in an invalid Interval.");
    }


    /++
        Params:
            rhs = The $(LREF2 .Interval, Interval) to assign to this one.
      +/
    ref Interval opAssign(const ref Interval rhs) pure nothrow
    {
        _begin = cast(TP)rhs._begin;
        _end = cast(TP)rhs._end;
        return this;
    }


    /++
        Params:
            rhs = The $(LREF2 .Interval, Interval) to assign to this one.
      +/
    ref Interval opAssign(Interval rhs) pure nothrow
    {
        _begin = cast(TP)rhs._begin;
        _end = cast(TP)rhs._end;
        return this;
    }


    /++
        The starting point of the interval. It is included in the interval.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).begin ==
       Date(1996, 1, 2));
--------------------
      +/
    @property TP begin() const pure nothrow
    {
        return cast(TP)_begin;
    }


    /++
        The starting point of the interval. It is included in the interval.

        Params:
            timePoint = The time point to set $(D begin) to.

        Throws:
            $(LREF DateTimeException) if the resulting interval would be invalid.
      +/
    @property void begin(TP timePoint) pure
    {
        if(!_valid(timePoint, _end))
            throw new DateTimeException("Arguments would result in an invalid Interval.");

        _begin = timePoint;
    }


    /++
        The end point of the interval. It is excluded from the interval.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).end ==
       Date(2012, 3, 1));
--------------------
      +/
    @property TP end() const pure nothrow
    {
        return cast(TP)_end;
    }


    /++
        The end point of the interval. It is excluded from the interval.

        Params:
            timePoint = The time point to set end to.

        Throws:
            $(LREF DateTimeException) if the resulting interval would be invalid.
      +/
    @property void end(TP timePoint) pure
    {
        if(!_valid(_begin, timePoint))
            throw new DateTimeException("Arguments would result in an invalid Interval.");

        _end = timePoint;
    }


    /++
        Returns the duration between $(D begin) and $(D end).

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length ==
       dur!"days"(5903));
--------------------
      +/
    @property auto length() const pure nothrow
    {
        return _end - _begin;
    }


    /++
        Whether the interval's length is 0, that is, whether $(D begin == end).

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(1996, 1, 2)).empty);
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).empty);
--------------------
      +/
    @property bool empty() const pure nothrow
    {
        return _begin == _end;
    }


    /++
        Whether the given time point is within this interval.

        Params:
            timePoint = The time point to check for inclusion in this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            Date(1994, 12, 24)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            Date(2000, 1, 5)));
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            Date(2012, 3, 1)));
--------------------
      +/
    bool contains(in TP timePoint) const pure
    {
        _enforceNotEmpty();

        return timePoint >= _begin && timePoint < _end;
    }


    /++
        Whether the given interval is completely within this interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Throws:
            $(LREF DateTimeException) if either interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
--------------------
      +/
    bool contains(in Interval interval) const pure
    {
        _enforceNotEmpty();
        interval._enforceNotEmpty();

        return interval._begin >= _begin &&
               interval._begin < _end &&
               interval._end <= _end;
    }


    /++
        Whether the given interval is completely within this interval.

        Always returns false (unless this interval is empty), because an
        interval going to positive infinity can never be contained in a finite
        interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            PosInfInterval!Date(Date(1999, 5, 4))));
--------------------
      +/
    bool contains(in PosInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return false;
    }


    /++
        Whether the given interval is completely within this interval.

        Always returns false (unless this interval is empty), because an
        interval beginning at negative infinity can never be contained in a
        finite interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(
            NegInfInterval!Date(Date(1996, 5, 4))));
--------------------
      +/
    bool contains(in NegInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return false;
    }


    /++
        Whether this interval is before the given time point.

        Params:
            timePoint = The time point to check whether this interval is before
                        it.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            Date(1994, 12, 24)));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            Date(2000, 1, 5)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            Date(2012, 3, 1)));
--------------------
      +/
    bool isBefore(in TP timePoint) const pure
    {
        _enforceNotEmpty();

        return _end <= timePoint;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect with it.

        Params:
            interval = The interval to check for against this interval.

        Throws:
            $(LREF DateTimeException) if either interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1))));
--------------------
      +/
    bool isBefore(in Interval interval) const pure
    {
        _enforceNotEmpty();
        interval._enforceNotEmpty();

        return _end <= interval._begin;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect with it.

        Params:
            interval = The interval to check for against this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            PosInfInterval!Date(Date(2013, 3, 7))));
--------------------
      +/
    bool isBefore(in PosInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return _end <= interval._begin;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect with it.

        Always returns false (unless this interval is empty) because a finite
        interval can never be before an interval beginning at negative infinity.

        Params:
            interval = The interval to check for against this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(
            NegInfInterval!Date(Date(1996, 5, 4))));
--------------------
      +/
    bool isBefore(in NegInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return false;
    }


    /++
        Whether this interval is after the given time point.

        Params:
            timePoint = The time point to check whether this interval is after
                        it.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            Date(1994, 12, 24)));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            Date(2000, 1, 5)));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            Date(2012, 3, 1)));
--------------------
      +/
    bool isAfter(in TP timePoint) const pure
    {
        _enforceNotEmpty();

        return timePoint < _begin;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Params:
            interval = The interval to check against this interval.

        Throws:
            $(LREF DateTimeException) if either interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
--------------------
      +/
    bool isAfter(in Interval interval) const pure
    {
        _enforceNotEmpty();
        interval._enforceNotEmpty();

        return _begin >= interval._end;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Always returns false (unless this interval is empty) because a finite
        interval can never be after an interval going to positive infinity.

        Params:
            interval = The interval to check against this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            PosInfInterval!Date(Date(1999, 5, 4))));
--------------------
      +/
    bool isAfter(in PosInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return false;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Params:
            interval = The interval to check against this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(
            NegInfInterval!Date(Date(1996, 1, 2))));
--------------------
      +/
    bool isAfter(in NegInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return _begin >= interval._end;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this interval.

        Throws:
            $(LREF DateTimeException) if either interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
--------------------
      +/
    bool intersects(in Interval interval) const pure
    {
        _enforceNotEmpty();
        interval._enforceNotEmpty();

        return interval._begin < _end && interval._end > _begin;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            PosInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool intersects(in PosInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return _end > interval._begin;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            NegInfInterval!Date(Date(1996, 1, 2))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(
            NegInfInterval!Date(Date(2000, 1, 2))));
--------------------
      +/
    bool intersects(in NegInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return _begin < interval._end;
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect or if
            either interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
       Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));
--------------------
      +/
    Interval intersection(in Interval interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        auto begin = _begin > interval._begin ? _begin : interval._begin;
        auto end = _end < interval._end ? _end : interval._end;

        return Interval(begin, end);
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect or if
            this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
            PosInfInterval!Date(Date(1999, 1, 12))) ==
       Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
--------------------
      +/
    Interval intersection(in PosInfInterval!TP interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        return Interval(_begin > interval._begin ? _begin : interval._begin, _end);
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect or if
            this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
            NegInfInterval!Date(Date(1999, 7, 6))) ==
       Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
--------------------
      +/
    Interval intersection(in NegInfInterval!TP interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        return Interval(_begin, _end < interval._end ? _end : interval._end);
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Throws:
            $(LREF DateTimeException) if either interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(1990, 7, 6), Date(1996, 1, 2))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(2012, 3, 1), Date(2013, 9, 17))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1))));
--------------------
      +/
    bool isAdjacent(in Interval interval) const pure
    {
        _enforceNotEmpty();
        interval._enforceNotEmpty();

        return _begin == interval._end || _end == interval._begin;
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            PosInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool isAdjacent(in PosInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return _end == interval._begin;
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            NegInfInterval!Date(Date(1996, 1, 2))));

assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(
            NegInfInterval!Date(Date(2000, 1, 2))));
--------------------
      +/
    bool isAdjacent(in NegInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return _begin == interval._end;
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect and are
            not adjacent or if either interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
            Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));
--------------------
      +/
    Interval merge(in Interval interval) const
    {
        import std.format : format;

        enforce(this.isAdjacent(interval) || this.intersects(interval),
                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));

        auto begin = _begin < interval._begin ? _begin : interval._begin;
        auto end = _end > interval._end ? _end : interval._end;

        return Interval(begin, end);
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect and are
            not adjacent or if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       PosInfInterval!Date(Date(1990, 7 , 6)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
            PosInfInterval!Date(Date(2012, 3, 1))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));
--------------------
      +/
    PosInfInterval!TP merge(in PosInfInterval!TP interval) const
    {
        import std.format : format;

        enforce(this.isAdjacent(interval) || this.intersects(interval),
                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));

        return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin);
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect and are not
            adjacent or if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
            NegInfInterval!Date(Date(1996, 1, 2))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       NegInfInterval!Date(Date(2013, 1 , 12)));
--------------------
      +/
    NegInfInterval!TP merge(in NegInfInterval!TP interval) const
    {
        import std.format : format;

        enforce(this.isAdjacent(interval) || this.intersects(interval),
                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));

        return NegInfInterval!TP(_end > interval._end ? _end : interval._end);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this interval.

        Throws:
            $(LREF DateTimeException) if either interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
            Interval!Date(Date(1990, 7, 6), Date(1991, 1, 8))) ==
       Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
            Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));
--------------------
      +/
    Interval span(in Interval interval) const pure
    {
        _enforceNotEmpty();
        interval._enforceNotEmpty();

        auto begin = _begin < interval._begin ? _begin : interval._begin;
        auto end = _end > interval._end ? _end : interval._end;

        return Interval(begin, end);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       PosInfInterval!Date(Date(1990, 7 , 6)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
            PosInfInterval!Date(Date(2050, 1, 1))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));
--------------------
      +/
    PosInfInterval!TP span(in PosInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this interval.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Example:
--------------------
assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
            NegInfInterval!Date(Date(1602, 5, 21))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));

assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       NegInfInterval!Date(Date(2013, 1 , 12)));
--------------------
      +/
    NegInfInterval!TP span(in NegInfInterval!TP interval) const pure
    {
        _enforceNotEmpty();

        return NegInfInterval!TP(_end > interval._end ? _end : interval._end);
    }


    /++
        Shifts the interval forward or backwards in time by the given duration
        (a positive duration shifts the interval forward; a negative duration
        shifts it backward). Effectively, it does $(D begin += duration) and
        $(D end += duration).

        Params:
            duration = The duration to shift the interval by.

        Throws:
            $(LREF DateTimeException) this interval is empty or if the resulting
            interval would be invalid.

        Example:
--------------------
auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));
auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));

interval1.shift(dur!"days"(50));
assert(interval1 == Interval!Date(Date(1996, 2, 21), Date(2012, 5, 25)));

interval2.shift(dur!"days"(-50));
assert(interval2 == Interval!Date(Date(1995, 11, 13), Date(2012, 2, 15)));
--------------------
      +/
    void shift(D)(D duration) pure
        if(__traits(compiles, begin + duration))
    {
        _enforceNotEmpty();

        auto begin = _begin + duration;
        auto end = _end + duration;

        if(!_valid(begin, end))
            throw new DateTimeException("Argument would result in an invalid Interval.");

        _begin = begin;
        _end = end;
    }


    static if(__traits(compiles, begin.add!"months"(1)) &&
              __traits(compiles, begin.add!"years"(1)))
    {
        /++
            Shifts the interval forward or backwards in time by the given number
            of years and/or months (a positive number of years and months shifts
            the interval forward; a negative number shifts it backward).
            It adds the years the given years and months to both begin and end.
            It effectively calls $(D add!"years"()) and then $(D add!"months"())
            on begin and end with the given number of years and months.

            Params:
                years         = The number of years to shift the interval by.
                months        = The number of months to shift the interval by.
                allowOverflow = Whether the days should be allowed to overflow
                                on $(D begin) and $(D end), causing their month
                                to increment.

            Throws:
                $(LREF DateTimeException) if this interval is empty or if the
                resulting interval would be invalid.

            Example:
--------------------
auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));

interval1.shift(2);
assert(interval1 == Interval!Date(Date(1998, 1, 2), Date(2014, 3, 1)));

interval2.shift(-2);
assert(interval2 == Interval!Date(Date(1994, 1, 2), Date(2010, 3, 1)));
--------------------
          +/
        void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
            if(isIntegral!T)
        {
            _enforceNotEmpty();

            auto begin = _begin;
            auto end = _end;

            begin.add!"years"(years, allowOverflow);
            begin.add!"months"(months, allowOverflow);
            end.add!"years"(years, allowOverflow);
            end.add!"months"(months, allowOverflow);

            enforce(_valid(begin, end), new DateTimeException("Argument would result in an invalid Interval."));

            _begin = begin;
            _end = end;
        }
    }


    /++
        Expands the interval forwards and/or backwards in time. Effectively,
        it does $(D begin -= duration) and/or $(D end += duration). Whether
        it expands forwards and/or backwards in time is determined by
        $(D_PARAM dir).

        Params:
            duration = The duration to expand the interval by.
            dir      = The direction in time to expand the interval.

        Throws:
            $(LREF DateTimeException) this interval is empty or if the resulting
            interval would be invalid.

        Example:
--------------------
auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));

interval1.expand(2);
assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1)));

interval2.expand(-2);
assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1)));
--------------------
      +/
    void expand(D)(D duration, Direction dir = Direction.both) pure
        if(__traits(compiles, begin + duration))
    {
        _enforceNotEmpty();

        switch(dir)
        {
            case Direction.both:
            {
                auto begin = _begin - duration;
                auto end = _end + duration;

                if(!_valid(begin, end))
                    throw new DateTimeException("Argument would result in an invalid Interval.");

                _begin = begin;
                _end = end;

                return;
            }
            case Direction.fwd:
            {
                auto end = _end + duration;

                if(!_valid(_begin, end))
                    throw new DateTimeException("Argument would result in an invalid Interval.");
                _end = end;

                return;
            }
            case Direction.bwd:
            {
                auto begin = _begin - duration;

                if(!_valid(begin, _end))
                    throw new DateTimeException("Argument would result in an invalid Interval.");
                _begin = begin;

                return;
            }
            default:
                assert(0, "Invalid Direction.");
        }
    }

    static if(__traits(compiles, begin.add!"months"(1)) &&
              __traits(compiles, begin.add!"years"(1)))
    {
        /++
            Expands the interval forwards and/or backwards in time. Effectively,
            it subtracts the given number of months/years from $(D begin) and
            adds them to $(D end). Whether it expands forwards and/or backwards
            in time is determined by $(D_PARAM dir).

            Params:
                years         = The number of years to expand the interval by.
                months        = The number of months to expand the interval by.
                allowOverflow = Whether the days should be allowed to overflow
                                on $(D begin) and $(D end), causing their month
                                to increment.
                dir           = The direction in time to expand the interval.

            Throws:
                $(LREF DateTimeException) if this interval is empty or if the
                resulting interval would be invalid.

            Example:
--------------------
auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));

interval1.expand(2);
assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1)));

interval2.expand(-2);
assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1)));
--------------------
          +/
        void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes, Direction dir = Direction.both)
            if(isIntegral!T)
        {
            _enforceNotEmpty();

            switch(dir)
            {
                case Direction.both:
                {
                    auto begin = _begin;
                    auto end = _end;

                    begin.add!"years"(-years, allowOverflow);
                    begin.add!"months"(-months, allowOverflow);
                    end.add!"years"(years, allowOverflow);
                    end.add!"months"(months, allowOverflow);

                    enforce(_valid(begin, end), new DateTimeException("Argument would result in an invalid Interval."));
                    _begin = begin;
                    _end = end;

                    return;
                }
                case Direction.fwd:
                {
                    auto end = _end;

                    end.add!"years"(years, allowOverflow);
                    end.add!"months"(months, allowOverflow);

                    enforce(_valid(_begin, end), new DateTimeException("Argument would result in an invalid Interval."));
                    _end = end;

                    return;
                }
                case Direction.bwd:
                {
                    auto begin = _begin;

                    begin.add!"years"(-years, allowOverflow);
                    begin.add!"months"(-months, allowOverflow);

                    enforce(_valid(begin, _end), new DateTimeException("Argument would result in an invalid Interval."));
                    _begin = begin;

                    return;
                }
                default:
                    assert(0, "Invalid Direction.");
            }
        }
    }


    /++
        Returns a range which iterates forward over the interval, starting
        at $(D begin), using $(D_PARAM func) to generate each successive time
        point.

        The range's $(D front) is the interval's $(D begin). $(D_PARAM func) is
        used to generate the next $(D front) when $(D popFront) is called. If
        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
        before the range is returned (so that $(D front) is a time point which
        $(D_PARAM func) would generate).

        If $(D_PARAM func) ever generates a time point less than or equal to the
        current $(D front) of the range, then a $(LREF DateTimeException) will be
        thrown. The range will be empty and iteration complete when
        $(D_PARAM func) generates a time point equal to or beyond the $(D end)
        of the interval.

        There are helper functions in this module which generate common
        delegates to pass to $(D fwdRange). Their documentation starts with
        "Range-generating function," making them easily searchable.

        Params:
            func     = The function used to generate the time points of the
                       range over the interval.
            popFirst = Whether $(D popFront) should be called on the range
                       before returning it.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Warning:
            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
            would be a function pointer to a pure function, but forcing
            $(D_PARAM func) to be pure is far too restrictive to be useful, and
            in order to have the ease of use of having functions which generate
            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
            delegate.

            If $(D_PARAM func) retains state which changes as it is called, then
            some algorithms will not work correctly, because the range's
            $(D save) will have failed to have really saved the range's state.
            To avoid such bugs, don't pass a delegate which is
            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
            same time point with two different calls, it must return the same
            result both times.

            Of course, none of the functions in this module have this problem,
            so it's only relevant if when creating a custom delegate.

        Example:
--------------------
auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
auto func = delegate (in Date date) //For iterating over even-numbered days.
            {
                if((date.day & 1) == 0)
                    return date + dur!"days"(2);

                return date + dur!"days"(1);
            };
auto range = interval.fwdRange(func);

 //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).
assert(range.front == Date(2010, 9, 1));

range.popFront();
assert(range.front == Date(2010, 9, 2));

range.popFront();
assert(range.front == Date(2010, 9, 4));

range.popFront();
assert(range.front == Date(2010, 9, 6));

range.popFront();
assert(range.front == Date(2010, 9, 8));

range.popFront();
assert(range.empty);
--------------------
      +/
    IntervalRange!(TP, Direction.fwd) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
    {
        _enforceNotEmpty();

        auto range = IntervalRange!(TP, Direction.fwd)(this, func);

        if(popFirst == PopFirst.yes)
            range.popFront();

        return range;
    }


    /++
        Returns a range which iterates backwards over the interval, starting
        at $(D end), using $(D_PARAM func) to generate each successive time
        point.

        The range's $(D front) is the interval's $(D end). $(D_PARAM func) is
        used to generate the next $(D front) when $(D popFront) is called. If
        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
        before the range is returned (so that $(D front) is a time point which
        $(D_PARAM func) would generate).

        If $(D_PARAM func) ever generates a time point greater than or equal to
        the current $(D front) of the range, then a $(LREF DateTimeException) will
        be thrown. The range will be empty and iteration complete when
        $(D_PARAM func) generates a time point equal to or less than the
        $(D begin) of the interval.

        There are helper functions in this module which generate common
        delegates to pass to $(D bwdRange). Their documentation starts with
        "Range-generating function," making them easily searchable.

        Params:
            func     = The function used to generate the time points of the
                       range over the interval.
            popFirst = Whether $(D popFront) should be called on the range
                       before returning it.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Warning:
            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
            would be a function pointer to a pure function, but forcing
            $(D_PARAM func) to be pure is far too restrictive to be useful, and
            in order to have the ease of use of having functions which generate
            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
            delegate.

            If $(D_PARAM func) retains state which changes as it is called, then
            some algorithms will not work correctly, because the range's
            $(D save) will have failed to have really saved the range's state.
            To avoid such bugs, don't pass a delegate which is
            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
            same time point with two different calls, it must return the same
            result both times.

            Of course, none of the functions in this module have this problem,
            so it's only relevant for custom delegates.

        Example:
--------------------
auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
auto func = delegate (in Date date) //For iterating over even-numbered days.
            {
                if((date.day & 1) == 0)
                    return date - dur!"days"(2);

                return date - dur!"days"(1);
            };
auto range = interval.bwdRange(func);

//An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).
assert(range.front == Date(2010, 9, 9));

range.popFront();
assert(range.front == Date(2010, 9, 8));

range.popFront();
assert(range.front == Date(2010, 9, 6));

range.popFront();
assert(range.front == Date(2010, 9, 4));

range.popFront();
assert(range.front == Date(2010, 9, 2));

range.popFront();
assert(range.empty);
--------------------
      +/
    IntervalRange!(TP, Direction.bwd) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
    {
        _enforceNotEmpty();

        auto range = IntervalRange!(TP, Direction.bwd)(this, func);

        if(popFirst == PopFirst.yes)
            range.popFront();

        return range;
    }


    /+
        Converts this interval to a string.
      +/
    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
    //have versions of toString() with extra modifiers, so we define one version
    //with modifiers and one without.
    string toString()
    {
        return _toStringImpl();
    }


    /++
        Converts this interval to a string.
      +/
    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
    //have versions of toString() with extra modifiers, so we define one version
    //with modifiers and one without.
    string toString() const nothrow
    {
        return _toStringImpl();
    }


private:

    /+
        Since we have two versions of toString, we have _toStringImpl
        so that they can share implementations.
      +/
    string _toStringImpl() const nothrow
    {
        import std.format : format;
        try
            return format("[%s - %s)", _begin, _end);
        catch(Exception e)
            assert(0, "format() threw.");
    }


    /+
        Throws:
            $(LREF DateTimeException) if this interval is empty.
      +/
    void _enforceNotEmpty(size_t line = __LINE__) const pure
    {
        if(empty)
            throw new DateTimeException("Invalid operation for an empty Interval.", __FILE__, line);
    }


    /+
        Whether the given values form a valid time interval.

        Params:
            begin = The starting point of the interval.
            end   = The end point of the interval.
     +/
    static bool _valid(in TP begin, in TP end) pure nothrow
    {
        return begin <= end;
    }


    pure invariant()
    {
        assert(_valid(_begin, _end), "Invariant Failure: begin is not before or equal to end.");
    }


    TP _begin;
    TP _end;
}

//Test Interval's constructors.
unittest
{
    assertThrown!DateTimeException(Interval!Date(Date(2010, 1, 1), Date(1, 1, 1)));

    Interval!Date(Date.init, Date.init);
    Interval!TimeOfDay(TimeOfDay.init, TimeOfDay.init);
    Interval!DateTime(DateTime.init, DateTime.init);
    Interval!SysTime(SysTime(0), SysTime(0));

    Interval!DateTime(DateTime.init, dur!"days"(7));

    //Verify Examples.
    Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
    assert(Interval!Date(Date(1996, 1, 2), dur!"weeks"(3)) == Interval!Date(Date(1996, 1, 2), Date(1996, 1, 23)));
    assert(Interval!Date(Date(1996, 1, 2), dur!"days"(3)) == Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5)));
    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"hours"(3)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 15, 0, 0)));
    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"minutes"(3)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 3, 0)));
    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"seconds"(3)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 0, 3)));
    assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"msecs"(3000)) == Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 0, 3)));
}

//Test Interval's begin.
unittest
{
    assert(Interval!Date(Date(1, 1, 1), Date(2010, 1, 1)).begin == Date(1, 1, 1));
    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).begin == Date(2010, 1, 1));
    assert(Interval!Date(Date(1997, 12, 31), Date(1998, 1, 1)).begin == Date(1997, 12, 31));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(cInterval.begin == Date(2010, 7, 4));
    assert(iInterval.begin == Date(2010, 7, 4));

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).begin == Date(1996, 1, 2));
}

//Test Interval's end.
unittest
{
    assert(Interval!Date(Date(1, 1, 1), Date(2010, 1, 1)).end == Date(2010, 1, 1));
    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).end == Date(2010, 1, 1));
    assert(Interval!Date(Date(1997, 12, 31), Date(1998, 1, 1)).end == Date(1998, 1, 1));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(cInterval.end == Date(2012, 1, 7));
    assert(iInterval.end == Date(2012, 1, 7));

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).end == Date(2012, 3, 1));
}

//Test Interval's length.
unittest
{
    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).length == dur!"days"(0));
    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 4, 1)).length == dur!"days"(90));
    assert(Interval!TimeOfDay(TimeOfDay(0, 30, 0), TimeOfDay(12, 22, 7)).length == dur!"seconds"(42_727));
    assert(Interval!DateTime(DateTime(2010, 1, 1, 0, 30, 0), DateTime(2010, 1, 2, 12, 22, 7)).length == dur!"seconds"(129_127));
    assert(Interval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0)), SysTime(DateTime(2010, 1, 2, 12, 22, 7))).length == dur!"seconds"(129_127));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(cInterval.length != Duration.zero);
    assert(iInterval.length != Duration.zero);

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length == dur!"days"(5903));
}

//Test Interval's empty.
unittest
{
    assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).empty);
    assert(!Interval!Date(Date(2010, 1, 1), Date(2010, 4, 1)).empty);
    assert(!Interval!TimeOfDay(TimeOfDay(0, 30, 0), TimeOfDay(12, 22, 7)).empty);
    assert(!Interval!DateTime(DateTime(2010, 1, 1, 0, 30, 0), DateTime(2010, 1, 2, 12, 22, 7)).empty);
    assert(!Interval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0)), SysTime(DateTime(2010, 1, 2, 12, 22, 7))).empty);

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(!cInterval.empty);
    assert(!iInterval.empty);

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(1996, 1, 2)).empty);
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).empty);
}

//Test Interval's contains(time point).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(Date(2010, 7, 4)));

    assert(!interval.contains(Date(2009, 7, 4)));
    assert(!interval.contains(Date(2010, 7, 3)));
    assert(interval.contains(Date(2010, 7, 4)));
    assert(interval.contains(Date(2010, 7, 5)));
    assert(interval.contains(Date(2011, 7, 1)));
    assert(interval.contains(Date(2012, 1, 6)));
    assert(!interval.contains(Date(2012, 1, 7)));
    assert(!interval.contains(Date(2012, 1, 8)));
    assert(!interval.contains(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(interval.contains(cdate));
    assert(cInterval.contains(cdate));
    assert(iInterval.contains(cdate));

    //Verify Examples.
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(1994, 12, 24)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(2000, 1, 5)));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(2012, 3, 1)));
}

//Test Interval's contains(Interval).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(interval.contains(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(interval.contains(interval));
    assert(!interval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!interval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(interval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(interval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(interval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!interval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!interval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!interval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).contains(interval));
    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).contains(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).contains(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).contains(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).contains(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).contains(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).contains(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).contains(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).contains(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).contains(interval));
    assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).contains(interval));
    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).contains(interval));

    assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 8))));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(interval.contains(interval));
    assert(interval.contains(cInterval));
    assert(interval.contains(iInterval));
    assert(!interval.contains(posInfInterval));
    assert(!interval.contains(cPosInfInterval));
    assert(!interval.contains(iPosInfInterval));
    assert(!interval.contains(negInfInterval));
    assert(!interval.contains(cNegInfInterval));
    assert(!interval.contains(iNegInfInterval));
    assert(cInterval.contains(interval));
    assert(cInterval.contains(cInterval));
    assert(cInterval.contains(iInterval));
    assert(!cInterval.contains(posInfInterval));
    assert(!cInterval.contains(cPosInfInterval));
    assert(!cInterval.contains(iPosInfInterval));
    assert(!cInterval.contains(negInfInterval));
    assert(!cInterval.contains(cNegInfInterval));
    assert(!cInterval.contains(iNegInfInterval));
    assert(iInterval.contains(interval));
    assert(iInterval.contains(cInterval));
    assert(iInterval.contains(iInterval));
    assert(!iInterval.contains(posInfInterval));
    assert(!iInterval.contains(cPosInfInterval));
    assert(!iInterval.contains(iPosInfInterval));
    assert(!iInterval.contains(negInfInterval));
    assert(!iInterval.contains(cNegInfInterval));
    assert(!iInterval.contains(iNegInfInterval));

    //Verify Examples.
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(PosInfInterval!Date(Date(1999, 5, 4))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(1996, 5, 4))));
}

//Test Interval's isBefore(time point).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(Date(2010, 7, 4)));

    assert(!interval.isBefore(Date(2009, 7, 3)));
    assert(!interval.isBefore(Date(2010, 7, 3)));
    assert(!interval.isBefore(Date(2010, 7, 4)));
    assert(!interval.isBefore(Date(2010, 7, 5)));
    assert(!interval.isBefore(Date(2011, 7, 1)));
    assert(!interval.isBefore(Date(2012, 1, 6)));
    assert(interval.isBefore(Date(2012, 1, 7)));
    assert(interval.isBefore(Date(2012, 1, 8)));
    assert(interval.isBefore(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(!interval.isBefore(cdate));
    assert(!cInterval.isBefore(cdate));
    assert(!iInterval.isBefore(cdate));

    //Verify Examples.
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(1994, 12, 24)));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(2000, 1, 5)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(2012, 3, 1)));
}

//Test Interval's isBefore(Interval).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(interval.isBefore(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!interval.isBefore(interval));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!interval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!interval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!interval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(interval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(interval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isBefore(interval));
    assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isBefore(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isBefore(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isBefore(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isBefore(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isBefore(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isBefore(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isBefore(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isBefore(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isBefore(interval));
    assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isBefore(interval));
    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isBefore(interval));

    assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.isBefore(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(interval.isBefore(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(interval.isBefore(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 8))));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!interval.isBefore(interval));
    assert(!interval.isBefore(cInterval));
    assert(!interval.isBefore(iInterval));
    assert(!interval.isBefore(posInfInterval));
    assert(!interval.isBefore(cPosInfInterval));
    assert(!interval.isBefore(iPosInfInterval));
    assert(!interval.isBefore(negInfInterval));
    assert(!interval.isBefore(cNegInfInterval));
    assert(!interval.isBefore(iNegInfInterval));
    assert(!cInterval.isBefore(interval));
    assert(!cInterval.isBefore(cInterval));
    assert(!cInterval.isBefore(iInterval));
    assert(!cInterval.isBefore(posInfInterval));
    assert(!cInterval.isBefore(cPosInfInterval));
    assert(!cInterval.isBefore(iPosInfInterval));
    assert(!cInterval.isBefore(negInfInterval));
    assert(!cInterval.isBefore(cNegInfInterval));
    assert(!cInterval.isBefore(iNegInfInterval));
    assert(!iInterval.isBefore(interval));
    assert(!iInterval.isBefore(cInterval));
    assert(!iInterval.isBefore(iInterval));
    assert(!iInterval.isBefore(posInfInterval));
    assert(!iInterval.isBefore(cPosInfInterval));
    assert(!iInterval.isBefore(iPosInfInterval));
    assert(!iInterval.isBefore(negInfInterval));
    assert(!iInterval.isBefore(cNegInfInterval));
    assert(!iInterval.isBefore(iNegInfInterval));

    //Verify Examples.
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(2013, 3, 7))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(1996, 5, 4))));
}

//Test Interval's isAfter(time point).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(Date(2010, 7, 4)));

    assert(interval.isAfter(Date(2009, 7, 4)));
    assert(interval.isAfter(Date(2010, 7, 3)));
    assert(!interval.isAfter(Date(2010, 7, 4)));
    assert(!interval.isAfter(Date(2010, 7, 5)));
    assert(!interval.isAfter(Date(2011, 7, 1)));
    assert(!interval.isAfter(Date(2012, 1, 6)));
    assert(!interval.isAfter(Date(2012, 1, 7)));
    assert(!interval.isAfter(Date(2012, 1, 8)));
    assert(!interval.isAfter(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(!interval.isAfter(cdate));
    assert(!cInterval.isAfter(cdate));
    assert(!iInterval.isAfter(cdate));

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(1994, 12, 24)));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(2000, 1, 5)));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(2012, 3, 1)));
}

//Test Interval's isAfter(Interval).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(interval.isAfter(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!interval.isAfter(interval));
    assert(interval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!interval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!interval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isAfter(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isAfter(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isAfter(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isAfter(interval));
    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isAfter(interval));
    assert(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isAfter(interval));

    assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(interval.isAfter(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(interval.isAfter(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.isAfter(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 8))));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!interval.isAfter(interval));
    assert(!interval.isAfter(cInterval));
    assert(!interval.isAfter(iInterval));
    assert(!interval.isAfter(posInfInterval));
    assert(!interval.isAfter(cPosInfInterval));
    assert(!interval.isAfter(iPosInfInterval));
    assert(!interval.isAfter(negInfInterval));
    assert(!interval.isAfter(cNegInfInterval));
    assert(!interval.isAfter(iNegInfInterval));
    assert(!cInterval.isAfter(interval));
    assert(!cInterval.isAfter(cInterval));
    assert(!cInterval.isAfter(iInterval));
    assert(!cInterval.isAfter(posInfInterval));
    assert(!cInterval.isAfter(cPosInfInterval));
    assert(!cInterval.isAfter(iPosInfInterval));
    assert(!cInterval.isAfter(negInfInterval));
    assert(!cInterval.isAfter(cNegInfInterval));
    assert(!cInterval.isAfter(iNegInfInterval));
    assert(!iInterval.isAfter(interval));
    assert(!iInterval.isAfter(cInterval));
    assert(!iInterval.isAfter(iInterval));
    assert(!iInterval.isAfter(posInfInterval));
    assert(!iInterval.isAfter(cPosInfInterval));
    assert(!iInterval.isAfter(iPosInfInterval));
    assert(!iInterval.isAfter(negInfInterval));
    assert(!iInterval.isAfter(cNegInfInterval));
    assert(!iInterval.isAfter(iNegInfInterval));

    //Verify Examples.
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(1999, 5, 4))));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(1996, 1, 2))));
}

//Test Interval's intersects().
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(interval.intersects(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersects(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersects(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(interval.intersects(interval));
    assert(!interval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(interval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(interval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(interval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(interval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(interval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!interval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!interval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).intersects(interval));
    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).intersects(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).intersects(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).intersects(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).intersects(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).intersects(interval));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).intersects(interval));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).intersects(interval));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).intersects(interval));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).intersects(interval));
    assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).intersects(interval));
    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).intersects(interval));

    assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(interval.intersects(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.intersects(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.intersects(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!interval.intersects(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.intersects(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(interval.intersects(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 8))));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(interval.intersects(interval));
    assert(interval.intersects(cInterval));
    assert(interval.intersects(iInterval));
    assert(interval.intersects(posInfInterval));
    assert(interval.intersects(cPosInfInterval));
    assert(interval.intersects(iPosInfInterval));
    assert(interval.intersects(negInfInterval));
    assert(interval.intersects(cNegInfInterval));
    assert(interval.intersects(iNegInfInterval));
    assert(cInterval.intersects(interval));
    assert(cInterval.intersects(cInterval));
    assert(cInterval.intersects(iInterval));
    assert(cInterval.intersects(posInfInterval));
    assert(cInterval.intersects(cPosInfInterval));
    assert(cInterval.intersects(iPosInfInterval));
    assert(cInterval.intersects(negInfInterval));
    assert(cInterval.intersects(cNegInfInterval));
    assert(cInterval.intersects(iNegInfInterval));
    assert(iInterval.intersects(interval));
    assert(iInterval.intersects(cInterval));
    assert(iInterval.intersects(iInterval));
    assert(iInterval.intersects(posInfInterval));
    assert(iInterval.intersects(cPosInfInterval));
    assert(iInterval.intersects(iPosInfInterval));
    assert(iInterval.intersects(negInfInterval));
    assert(iInterval.intersects(cNegInfInterval));
    assert(iInterval.intersects(iNegInfInterval));

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(2012, 3, 1))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(1996, 1, 2))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(2000, 1, 2))));
}

//Test Interval's intersection().
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersection(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersection(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).intersection(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).intersection(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).intersection(interval));
    assertThrown!DateTimeException(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).intersection(interval));

    assertThrown!DateTimeException(interval.intersection(PosInfInterval!Date(Date(2012, 1, 7))));
    assertThrown!DateTimeException(interval.intersection(PosInfInterval!Date(Date(2012, 1, 8))));

    assertThrown!DateTimeException(interval.intersection(NegInfInterval!Date(Date(2010, 7, 3))));
    assertThrown!DateTimeException(interval.intersection(NegInfInterval!Date(Date(2010, 7, 4))));

    assert(interval.intersection(interval) == interval);
    assert(interval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
    assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
    assert(interval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
    assert(interval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
    assert(interval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));

    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).intersection(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).intersection(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).intersection(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).intersection(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).intersection(interval) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).intersection(interval) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).intersection(interval) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).intersection(interval) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));

    assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
    assert(interval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));

    assert(interval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
    assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 6)));
    assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!interval.intersection(interval).empty);
    assert(!interval.intersection(cInterval).empty);
    assert(!interval.intersection(iInterval).empty);
    assert(!interval.intersection(posInfInterval).empty);
    assert(!interval.intersection(cPosInfInterval).empty);
    assert(!interval.intersection(iPosInfInterval).empty);
    assert(!interval.intersection(negInfInterval).empty);
    assert(!interval.intersection(cNegInfInterval).empty);
    assert(!interval.intersection(iNegInfInterval).empty);
    assert(!cInterval.intersection(interval).empty);
    assert(!cInterval.intersection(cInterval).empty);
    assert(!cInterval.intersection(iInterval).empty);
    assert(!cInterval.intersection(posInfInterval).empty);
    assert(!cInterval.intersection(cPosInfInterval).empty);
    assert(!cInterval.intersection(iPosInfInterval).empty);
    assert(!cInterval.intersection(negInfInterval).empty);
    assert(!cInterval.intersection(cNegInfInterval).empty);
    assert(!cInterval.intersection(iNegInfInterval).empty);
    assert(!iInterval.intersection(interval).empty);
    assert(!iInterval.intersection(cInterval).empty);
    assert(!iInterval.intersection(iInterval).empty);
    assert(!iInterval.intersection(posInfInterval).empty);
    assert(!iInterval.intersection(cPosInfInterval).empty);
    assert(!iInterval.intersection(iPosInfInterval).empty);
    assert(!iInterval.intersection(negInfInterval).empty);
    assert(!iInterval.intersection(cNegInfInterval).empty);
    assert(!iInterval.intersection(iNegInfInterval).empty);

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1)));
}

//Test Interval's isAdjacent().
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    static void testInterval(in Interval!Date interval1, in Interval!Date interval2)
    {
        interval1.isAdjacent(interval2);
    }

    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!interval.isAdjacent(interval));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(interval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isAdjacent(interval));
    assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isAdjacent(interval));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isAdjacent(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isAdjacent(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isAdjacent(interval));
    assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isAdjacent(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isAdjacent(interval));
    assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isAdjacent(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isAdjacent(interval));
    assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isAdjacent(interval));
    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isAdjacent(interval));
    assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isAdjacent(interval));

    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8))));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!interval.isAdjacent(interval));
    assert(!interval.isAdjacent(cInterval));
    assert(!interval.isAdjacent(iInterval));
    assert(!interval.isAdjacent(posInfInterval));
    assert(!interval.isAdjacent(cPosInfInterval));
    assert(!interval.isAdjacent(iPosInfInterval));
    assert(!interval.isAdjacent(negInfInterval));
    assert(!interval.isAdjacent(cNegInfInterval));
    assert(!interval.isAdjacent(iNegInfInterval));
    assert(!cInterval.isAdjacent(interval));
    assert(!cInterval.isAdjacent(cInterval));
    assert(!cInterval.isAdjacent(iInterval));
    assert(!cInterval.isAdjacent(posInfInterval));
    assert(!cInterval.isAdjacent(cPosInfInterval));
    assert(!cInterval.isAdjacent(iPosInfInterval));
    assert(!cInterval.isAdjacent(negInfInterval));
    assert(!cInterval.isAdjacent(cNegInfInterval));
    assert(!cInterval.isAdjacent(iNegInfInterval));
    assert(!iInterval.isAdjacent(interval));
    assert(!iInterval.isAdjacent(cInterval));
    assert(!iInterval.isAdjacent(iInterval));
    assert(!iInterval.isAdjacent(posInfInterval));
    assert(!iInterval.isAdjacent(cPosInfInterval));
    assert(!iInterval.isAdjacent(iPosInfInterval));
    assert(!iInterval.isAdjacent(negInfInterval));
    assert(!iInterval.isAdjacent(cNegInfInterval));
    assert(!iInterval.isAdjacent(iNegInfInterval));

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1990, 7, 6), Date(1996, 1, 2))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2012, 3, 1), Date(2013, 9, 17))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1))));

    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(2012, 3, 1))));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(1996, 1, 2))));
    assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(2000, 1, 2))));
}

//Test Interval's merge().
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    static void testInterval(I)(in Interval!Date interval1, in I interval2)
    {
        interval1.merge(interval2);
    }

    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)), interval));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)), interval));

    assertThrown!DateTimeException(testInterval(interval, PosInfInterval!Date(Date(2012, 1, 8))));

    assertThrown!DateTimeException(testInterval(interval, NegInfInterval!Date(Date(2010, 7, 3))));

    assert(interval.merge(interval) == interval);
    assert(interval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
    assert(interval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(interval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));

    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).merge(interval) ==
                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).merge(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).merge(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).merge(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).merge(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).merge(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).merge(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).merge(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).merge(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).merge(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));

    assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.merge(PosInfInterval!Date(Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.merge(PosInfInterval!Date(Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    assert(interval.merge(NegInfInterval!Date(Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.merge(NegInfInterval!Date(Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!interval.merge(interval).empty);
    assert(!interval.merge(cInterval).empty);
    assert(!interval.merge(iInterval).empty);
    assert(!interval.merge(posInfInterval).empty);
    assert(!interval.merge(cPosInfInterval).empty);
    assert(!interval.merge(iPosInfInterval).empty);
    assert(!interval.merge(negInfInterval).empty);
    assert(!interval.merge(cNegInfInterval).empty);
    assert(!interval.merge(iNegInfInterval).empty);
    assert(!cInterval.merge(interval).empty);
    assert(!cInterval.merge(cInterval).empty);
    assert(!cInterval.merge(iInterval).empty);
    assert(!cInterval.merge(posInfInterval).empty);
    assert(!cInterval.merge(cPosInfInterval).empty);
    assert(!cInterval.merge(iPosInfInterval).empty);
    assert(!cInterval.merge(negInfInterval).empty);
    assert(!cInterval.merge(cNegInfInterval).empty);
    assert(!cInterval.merge(iNegInfInterval).empty);
    assert(!iInterval.merge(interval).empty);
    assert(!iInterval.merge(cInterval).empty);
    assert(!iInterval.merge(iInterval).empty);
    assert(!iInterval.merge(posInfInterval).empty);
    assert(!iInterval.merge(cPosInfInterval).empty);
    assert(!iInterval.merge(iPosInfInterval).empty);
    assert(!iInterval.merge(negInfInterval).empty);
    assert(!iInterval.merge(cNegInfInterval).empty);
    assert(!iInterval.merge(iNegInfInterval).empty);

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(PosInfInterval!Date(Date(2012, 3, 1))) == PosInfInterval!Date(Date(1996, 1 , 2)));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(1996, 1, 2))) == NegInfInterval!Date(Date(2012, 3 , 1)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
}

//Test Interval's span().
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    static void testInterval(in Interval!Date interval1, in Interval!Date interval2)
    {
        interval1.span(interval2);
    }

    assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval));
    assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(interval.span(interval) == interval);
    assert(interval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
                Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
    assert(interval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(interval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(interval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(interval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 9)));

    assert(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).span(interval) ==
                Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).span(interval) ==
                Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).span(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).span(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).span(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).span(interval) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).span(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).span(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).span(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).span(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).span(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).span(interval) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 9)));

    assert(interval.span(PosInfInterval!Date(Date(2010, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(interval.span(PosInfInterval!Date(Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.span(PosInfInterval!Date(Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.span(PosInfInterval!Date(Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.span(PosInfInterval!Date(Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(interval.span(PosInfInterval!Date(Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    assert(interval.span(NegInfInterval!Date(Date(2010, 7, 3))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.span(NegInfInterval!Date(Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.span(NegInfInterval!Date(Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.span(NegInfInterval!Date(Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.span(NegInfInterval!Date(Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(interval.span(NegInfInterval!Date(Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!interval.span(interval).empty);
    assert(!interval.span(cInterval).empty);
    assert(!interval.span(iInterval).empty);
    assert(!interval.span(posInfInterval).empty);
    assert(!interval.span(cPosInfInterval).empty);
    assert(!interval.span(iPosInfInterval).empty);
    assert(!interval.span(negInfInterval).empty);
    assert(!interval.span(cNegInfInterval).empty);
    assert(!interval.span(iNegInfInterval).empty);
    assert(!cInterval.span(interval).empty);
    assert(!cInterval.span(cInterval).empty);
    assert(!cInterval.span(iInterval).empty);
    assert(!cInterval.span(posInfInterval).empty);
    assert(!cInterval.span(cPosInfInterval).empty);
    assert(!cInterval.span(iPosInfInterval).empty);
    assert(!cInterval.span(negInfInterval).empty);
    assert(!cInterval.span(cNegInfInterval).empty);
    assert(!cInterval.span(iNegInfInterval).empty);
    assert(!iInterval.span(interval).empty);
    assert(!iInterval.span(cInterval).empty);
    assert(!iInterval.span(iInterval).empty);
    assert(!iInterval.span(posInfInterval).empty);
    assert(!iInterval.span(cPosInfInterval).empty);
    assert(!iInterval.span(iPosInfInterval).empty);
    assert(!iInterval.span(negInfInterval).empty);
    assert(!iInterval.span(cNegInfInterval).empty);
    assert(!iInterval.span(iNegInfInterval).empty);

    //Verify Examples.
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(Interval!Date(Date(1990, 7, 6), Date(1991, 1, 8))) == Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7)));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(PosInfInterval!Date(Date(2050, 1, 1))) == PosInfInterval!Date(Date(1996, 1 , 2)));

    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(NegInfInterval!Date(Date(1602, 5, 21))) == NegInfInterval!Date(Date(2012, 3 , 1)));
    assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
}

//Test Interval's shift(duration).
unittest
{
    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

    static void testIntervalFail(Interval!Date interval, in Duration duration)
    {
        interval.shift(duration);
    }

    assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), dur!"days"(1)));

    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
    {
        interval.shift(duration);
        assert(interval == expected);
    }

    testInterval(interval, dur!"days"(22), Interval!Date(Date(2010, 7, 26), Date(2012, 1, 29)));
    testInterval(interval, dur!"days"(-22), Interval!Date(Date(2010, 6, 12), Date(2011, 12, 16)));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    static assert(!__traits(compiles, cInterval.shift(dur!"days"(5))));
    static assert(!__traits(compiles, iInterval.shift(dur!"days"(5))));

    //Verify Examples.
    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));
    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5));

    interval1.shift(dur!"days"(50));
    assert(interval1 == Interval!Date(Date(1996, 2, 21), Date(2012, 5, 25)));

    interval2.shift(dur!"days"(-50));
    assert(interval2 == Interval!Date(Date(1995, 11, 13), Date(2012, 2, 15)));
}

//Test Interval's shift(int, int, AllowDayOverflow).
unittest
{
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

        static void testIntervalFail(Interval!Date interval, int years, int months)
        {
            interval.shift(years, months);
        }

        assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), 1, 0));

        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
        {
            interval.shift(years, months, allow);
            assert(interval == expected);
        }

        testInterval(interval, 5, 0, AllowDayOverflow.yes, Interval!Date(Date(2015, 7, 4), Date(2017, 1, 7)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, Interval!Date(Date(2005, 7, 4), Date(2007, 1, 7)));

        auto interval2 = Interval!Date(Date(2000, 1, 29), Date(2010, 5, 31));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Interval!Date(Date(2001, 3, 1), Date(2011, 7, 1)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Interval!Date(Date(2000, 12, 29), Date(2011, 5, 1)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Interval!Date(Date(1998, 12, 29), Date(2009, 5, 1)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Interval!Date(Date(1999, 3, 1), Date(2009, 7, 1)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, Interval!Date(Date(2001, 2, 28), Date(2011, 6, 30)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, Interval!Date(Date(2000, 12, 29), Date(2011, 4, 30)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, Interval!Date(Date(1998, 12, 29), Date(2009, 4, 30)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, Interval!Date(Date(1999, 2, 28), Date(2009, 6, 30)));
    }

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    static assert(!__traits(compiles, cInterval.shift(5)));
    static assert(!__traits(compiles, iInterval.shift(5)));

    //Verify Examples.
    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));

    interval1.shift(2);
    assert(interval1 == Interval!Date(Date(1998, 1, 2), Date(2014, 3, 1)));

    interval2.shift(-2);
    assert(interval2 == Interval!Date(Date(1994, 1, 2), Date(2010, 3, 1)));
}

//Test Interval's expand(Duration).
unittest
{
    auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7));

    static void testIntervalFail(I)(I interval, in Duration duration)
    {
        interval.expand(duration);
    }

    assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), dur!"days"(1)));
    assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)), dur!"days"(-5)));

    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
    {
        interval.expand(duration);
        assert(interval == expected);
    }

    testInterval(interval, dur!"days"(22), Interval!Date(Date(2000, 6, 12), Date(2012, 1, 29)));
    testInterval(interval, dur!"days"(-22), Interval!Date(Date(2000, 7, 26), Date(2011, 12, 16)));

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    static assert(!__traits(compiles, cInterval.expand(dur!"days"(5))));
    static assert(!__traits(compiles, iInterval.expand(dur!"days"(5))));

    //Verify Examples.
    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));

    interval1.expand(dur!"days"(2));
    assert(interval1 == Interval!Date(Date(1995, 12, 31), Date(2012, 3, 3)));

    interval2.expand(dur!"days"(-2));
    assert(interval2 == Interval!Date(Date(1996, 1, 4), Date(2012, 2, 28)));
}

//Test Interval's expand(int, int, AllowDayOverflow, Direction)
unittest
{
    {
        auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7));

        static void testIntervalFail(Interval!Date interval, int years, int months)
        {
            interval.expand(years, months);
        }

        assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), 1, 0));
        assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)), -5, 0));

        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, Direction dir, in I expected, size_t line = __LINE__)
        {
            interval.expand(years, months, allow, dir);
            assert(interval == expected);
        }

        testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(1995, 7, 4), Date(2017, 1, 7)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(2005, 7, 4), Date(2007, 1, 7)));

        testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 7, 4), Date(2017, 1, 7)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 7, 4), Date(2007, 1, 7)));

        testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(1995, 7, 4), Date(2012, 1, 7)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(2005, 7, 4), Date(2012, 1, 7)));

        auto interval2 = Interval!Date(Date(2000, 1, 29), Date(2010, 5, 31));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(1998, 12, 29), Date(2011, 7, 1)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(1999, 3, 1), Date(2011, 5, 1)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(2001, 3, 1), Date(2009, 5, 1)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.both, Interval!Date(Date(2000, 12, 29), Date(2009, 7, 1)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(1998, 12, 29), Date(2011, 6, 30)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(1999, 2, 28), Date(2011, 4, 30)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(2001, 2, 28), Date(2009, 4, 30)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.both, Interval!Date(Date(2000, 12, 29), Date(2009, 6, 30)));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 7, 1)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 5, 1)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 5, 1)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 7, 1)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 6, 30)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2011, 4, 30)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 4, 30)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.fwd, Interval!Date(Date(2000, 1, 29), Date(2009, 6, 30)));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(1998, 12, 29), Date(2010, 5, 31)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(1999, 3, 1), Date(2010, 5, 31)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(2001, 3, 1), Date(2010, 5, 31)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.bwd, Interval!Date(Date(2000, 12, 29), Date(2010, 5, 31)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(1998, 12, 29), Date(2010, 5, 31)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(1999, 2, 28), Date(2010, 5, 31)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(2001, 2, 28), Date(2010, 5, 31)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.bwd, Interval!Date(Date(2000, 12, 29), Date(2010, 5, 31)));
    }

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    static assert(!__traits(compiles, cInterval.expand(5)));
    static assert(!__traits(compiles, iInterval.expand(5)));

    //Verify Examples.
    auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));
    auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1));

    interval1.expand(2);
    assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1)));

    interval2.expand(-2);
    assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1)));
}

//Test Interval's fwdRange.
unittest
{
    {
        auto interval = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21));

        static void testInterval1(Interval!Date interval)
        {
            interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
        }

        assertThrown!DateTimeException(testInterval1(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

        static void testInterval2(Interval!Date interval)
        {
            interval.fwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).popFront();
        }

        assertThrown!DateTimeException(testInterval2(interval));

        assert(!interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
        assert(interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).empty);

        assert(Interval!Date(Date(2010, 9, 12), Date(2010, 10, 1)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).front ==
                    Date(2010, 9, 12));

        assert(Interval!Date(Date(2010, 9, 12), Date(2010, 10, 1)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).front ==
                    Date(2010, 9, 17));
    }

    //Verify Examples.
    {
        auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
        auto func = delegate (in Date date)
                    {
                        if((date.day & 1) == 0)
                            return date + dur!"days"(2);

                        return date + dur!"days"(1);
                    };
        auto range = interval.fwdRange(func);

        assert(range.front == Date(2010, 9, 1)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).

        range.popFront();
        assert(range.front == Date(2010, 9, 2));

        range.popFront();
        assert(range.front == Date(2010, 9, 4));

        range.popFront();
        assert(range.front == Date(2010, 9, 6));

        range.popFront();
        assert(range.front == Date(2010, 9, 8));

        range.popFront();
        assert(range.empty);
    }

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(!cInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
    assert(!iInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
}

//Test Interval's bwdRange.
unittest
{
    {
        auto interval = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21));

        static void testInterval1(Interval!Date interval)
        {
            interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
        }

        assertThrown!DateTimeException(testInterval1(Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

        static void testInterval2(Interval!Date interval)
        {
            interval.bwdRange(everyDayOfWeek!(Date, Direction.fwd)(DayOfWeek.fri)).popFront();
        }

        assertThrown!DateTimeException(testInterval2(interval));

        assert(!interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty);
        assert(interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).empty);

        assert(Interval!Date(Date(2010, 9, 19), Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).front ==
                    Date(2010, 10, 1));

        assert(Interval!Date(Date(2010, 9, 19), Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).front ==
                    Date(2010, 9, 24));
    }

    //Verify Examples.
    {
        auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9));
        auto func = delegate (in Date date)
                    {
                        if((date.day & 1) == 0)
                            return date - dur!"days"(2);

                        return date - dur!"days"(1);
                    };
        auto range = interval.bwdRange(func);

        assert(range.front == Date(2010, 9, 9)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).

        range.popFront();
        assert(range.front == Date(2010, 9, 8));

        range.popFront();
        assert(range.front == Date(2010, 9, 6));

        range.popFront();
        assert(range.front == Date(2010, 9, 4));

        range.popFront();
        assert(range.front == Date(2010, 9, 2));

        range.popFront();
        assert(range.empty);
    }

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(!cInterval.bwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
    assert(!iInterval.bwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
}

//Test Interval's toString().
unittest
{
    assert(Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).toString() == "[2010-Jul-04 - 2012-Jan-07)");

    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(cInterval.toString());
    assert(iInterval.toString());
}


/++
    Represents an interval of time which has positive infinity as its end point.

    Any ranges which iterate over a $(D PosInfInterval) are infinite. So, the
    main purpose of using $(D PosInfInterval) is to create an infinite range
    which starts at a fixed point in time and goes to positive infinity.
  +/
struct PosInfInterval(TP)
{
public:

    /++
        Params:
            begin = The time point which begins the interval.

        Example:
--------------------
auto interval = PosInfInterval!Date(Date(1996, 1, 2));
--------------------
      +/
    this(in TP begin) pure nothrow
    {
        _begin = cast(TP)begin;
    }


    /++
        Params:
            rhs = The $(D PosInfInterval) to assign to this one.
      +/
    ref PosInfInterval opAssign(const ref PosInfInterval rhs) pure nothrow
    {
        _begin = cast(TP)rhs._begin;
        return this;
    }


    /++
        Params:
            rhs = The $(D PosInfInterval) to assign to this one.
      +/
    ref PosInfInterval opAssign(PosInfInterval rhs) pure nothrow
    {
        _begin = cast(TP)rhs._begin;
        return this;
    }


    /++
        The starting point of the interval. It is included in the interval.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2));
--------------------
      +/
    @property TP begin() const pure nothrow
    {
        return cast(TP)_begin;
    }


    /++
        The starting point of the interval. It is included in the interval.

        Params:
            timePoint = The time point to set $(D begin) to.
      +/
    @property void begin(TP timePoint) pure nothrow
    {
        _begin = timePoint;
    }


    /++
        Whether the interval's length is 0. Always returns false.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).empty);
--------------------
      +/
    @property bool empty() const pure nothrow
    {
        return false;
    }


    /++
        Whether the given time point is within this interval.

        Params:
            timePoint = The time point to check for inclusion in this interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(1994, 12, 24)));
assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(2000, 1, 5)));
--------------------
      +/
    bool contains(TP timePoint) const pure nothrow
    {
        return timePoint >= _begin;
    }


    /++
        Whether the given interval is completely within this interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(
            Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
--------------------
      +/
    bool contains(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return interval._begin >= _begin;
    }


    /++
        Whether the given interval is completely within this interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(
            PosInfInterval!Date(Date(1995, 7, 2))));
--------------------
      +/
    bool contains(in PosInfInterval interval) const pure nothrow
    {
        return interval._begin >= _begin;
    }


    /++
        Whether the given interval is completely within this interval.

        Always returns false because an interval going to positive infinity
        can never contain an interval beginning at negative infinity.

        Params:
            interval = The interval to check for inclusion in this interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(
            NegInfInterval!Date(Date(1996, 5, 4))));
--------------------
      +/
    bool contains(in NegInfInterval!TP interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is before the given time point.

        Always returns false because an interval going to positive infinity
        can never be before any time point.

        Params:
            timePoint = The time point to check whether this interval is before
                        it.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(1994, 12, 24)));
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(2000, 1, 5)));
--------------------
      +/
    bool isBefore(in TP timePoint) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect it.

        Always returns false (unless the given interval is empty) because an
        interval going to positive infinity can never be before any other
        interval.

        Params:
            interval = The interval to check for against this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
--------------------
      +/
    bool isBefore(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return false;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect it.

        Always returns false because an interval going to positive infinity can
        never be before any other interval.

        Params:
            interval = The interval to check for against this interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
            PosInfInterval!Date(Date(1992, 5, 4))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
            PosInfInterval!Date(Date(2013, 3, 7))));
--------------------
      +/
    bool isBefore(in PosInfInterval interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect it.

        Always returns false because an interval going to positive infinity can
        never be before any other interval.

        Params:
            interval = The interval to check for against this interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(
            NegInfInterval!Date(Date(1996, 5, 4))));
--------------------
      +/
    bool isBefore(in NegInfInterval!TP interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is after the given time point.

        Params:
            timePoint = The time point to check whether this interval is after
                        it.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(1994, 12, 24)));
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(2000, 1, 5)));
--------------------
      +/
    bool isAfter(in TP timePoint) const pure nothrow
    {
        return timePoint < _begin;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Params:
            interval = The interval to check against this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
--------------------
      +/
    bool isAfter(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return _begin >= interval._end;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Always returns false because an interval going to positive infinity can
        never be after another interval going to positive infinity.

        Params:
            interval = The interval to check against this interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            PosInfInterval!Date(Date(1990, 1, 7))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            PosInfInterval!Date(Date(1999, 5, 4))));
--------------------
      +/
    bool isAfter(in PosInfInterval interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Params:
            interval = The interval to check against this interval.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            NegInfInterval!Date(Date(1996, 1, 2))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(
            NegInfInterval!Date(Date(2000, 7, 1))));
--------------------
      +/
    bool isAfter(in NegInfInterval!TP interval) const pure nothrow
    {
        return _begin >= interval._end;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
--------------------
      +/
    bool intersects(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return interval._end > _begin;
    }


    /++
        Whether the given interval overlaps this interval.

        Always returns true because two intervals going to positive infinity
        always overlap.

        Params:
            interval = The interval to check for intersection with this
                       interval.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            PosInfInterval!Date(Date(1990, 1, 7))));

assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            PosInfInterval!Date(Date(1999, 5, 4))));
--------------------
      +/
    bool intersects(in PosInfInterval interval) const pure nothrow
    {
        return true;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this
                       interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            NegInfInterval!Date(Date(1996, 1, 2))));

assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(
            NegInfInterval!Date(Date(2000, 7, 1))));
--------------------
      +/
    bool intersects(in NegInfInterval!TP interval) const pure nothrow
    {
        return _begin < interval._end;
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect or if
            the given interval is empty.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
       Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));
--------------------
      +/
    Interval!TP intersection(in Interval!TP interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        auto begin = _begin > interval._begin ? _begin : interval._begin;

        return Interval!TP(begin, interval._end);
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
            PosInfInterval!Date(Date(1999, 1, 12))) ==
       PosInfInterval!Date(Date(1999, 1 , 12)));
--------------------
      +/
    PosInfInterval intersection(in PosInfInterval interval) const pure nothrow
    {
        return PosInfInterval(_begin < interval._begin ? interval._begin : _begin);
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
            NegInfInterval!Date(Date(1999, 7, 6))) ==
       Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       Interval!Date(Date(1996, 1 , 2), Date(2013, 1, 12)));
--------------------
      +/
    Interval!TP intersection(in NegInfInterval!TP interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        return Interval!TP(_begin, interval._end);
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
            Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));

assert(!PosInfInterval!Date(Date(1999, 1, 12)).isAdjacent(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
--------------------
      +/
    bool isAdjacent(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return _begin == interval._end;
    }


    /++
        Whether the given interval is adjacent to this interval.

        Always returns false because two intervals going to positive infinity
        can never be adjacent to one another.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Example:
--------------------
assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
            PosInfInterval!Date(Date(1990, 1, 7))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
            PosInfInterval!Date(Date(1996, 1, 2))));
--------------------
      +/
    bool isAdjacent(in PosInfInterval interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
            NegInfInterval!Date(Date(1996, 1, 2))));

assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(
            NegInfInterval!Date(Date(2000, 7, 1))));
--------------------
      +/
    bool isAdjacent(in NegInfInterval!TP interval) const pure nothrow
    {
        return _begin == interval._end;
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect and are
            not adjacent or if the given interval is empty.

        Note:
            There is no overload for $(D merge) which takes a
            $(D NegInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       PosInfInterval!Date(Date(1990, 7 , 6)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));
--------------------
      +/
    PosInfInterval merge(in Interval!TP interval) const
    {
        import std.format : format;

        enforce(this.isAdjacent(interval) || this.intersects(interval),
                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));

        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Note:
            There is no overload for $(D merge) which takes a
            $(D NegInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       PosInfInterval!Date(Date(1990, 7 , 6)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(
            PosInfInterval!Date(Date(1999, 1, 12))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));
--------------------
      +/
    PosInfInterval merge(in PosInfInterval interval) const pure nothrow
    {
        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this
                       interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Note:
            There is no overload for $(D span) which takes a
            $(D NegInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
            Interval!Date(Date(500, 8, 9), Date(1602, 1, 31))) ==
       PosInfInterval!Date(Date(500, 8, 9)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       PosInfInterval!Date(Date(1990, 7 , 6)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));
--------------------
      +/
    PosInfInterval span(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this
                       interval.

        Note:
            There is no overload for $(D span) which takes a
            $(D NegInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       PosInfInterval!Date(Date(1990, 7 , 6)));

assert(PosInfInterval!Date(Date(1996, 1, 2)).span(
            PosInfInterval!Date(Date(1999, 1, 12))) ==
       PosInfInterval!Date(Date(1996, 1 , 2)));
--------------------
      +/
    PosInfInterval span(in PosInfInterval interval) const pure nothrow
    {
        return PosInfInterval(_begin < interval._begin ? _begin : interval._begin);
    }


    /++
        Shifts the $(D begin) of this interval forward or backwards in time by
        the given duration (a positive duration shifts the interval forward; a
        negative duration shifts it backward). Effectively, it does
        $(D begin += duration).

        Params:
            duration = The duration to shift the interval by.

        Example:
--------------------
auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

interval1.shift(dur!"days"(50));
assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21)));

interval2.shift(dur!"days"(-50));
assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13)));
--------------------
      +/
    void shift(D)(D duration) pure nothrow
        if(__traits(compiles, begin + duration))
    {
        _begin += duration;
    }


    static if(__traits(compiles, begin.add!"months"(1)) &&
              __traits(compiles, begin.add!"years"(1)))
    {
        /++
            Shifts the $(D begin) of this interval forward or backwards in time
            by the given number of years and/or months (a positive number of years
            and months shifts the interval forward; a negative number shifts it
            backward). It adds the years the given years and months to
            $(D begin). It effectively calls $(D add!"years"()) and then
            $(D add!"months"()) on $(D begin) with the given number of years and
            months.

            Params:
                years         = The number of years to shift the interval by.
                months        = The number of months to shift the interval by.
                allowOverflow = Whether the days should be allowed to overflow
                                on $(D begin), causing its month to increment.

            Throws:
                $(LREF DateTimeException) if this interval is empty or if the
                resulting interval would be invalid.

            Example:
--------------------
auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

interval1.shift(dur!"days"(50));
assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21)));

interval2.shift(dur!"days"(-50));
assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13)));
--------------------
          +/
        void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
            if(isIntegral!T)
        {
            auto begin = _begin;

            begin.add!"years"(years, allowOverflow);
            begin.add!"months"(months, allowOverflow);

            _begin = begin;
        }
    }


    /++
        Expands the interval backwards in time. Effectively, it does
        $(D begin -= duration).

        Params:
            duration = The duration to expand the interval by.

        Example:
--------------------
auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

interval1.expand(dur!"days"(2));
assert(interval1 == PosInfInterval!Date(Date(1995, 12, 31)));

interval2.expand(dur!"days"(-2));
assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4)));
--------------------
      +/
    void expand(D)(D duration) pure nothrow
        if(__traits(compiles, begin + duration))
    {
        _begin -= duration;
    }


    static if(__traits(compiles, begin.add!"months"(1)) &&
              __traits(compiles, begin.add!"years"(1)))
    {
        /++
            Expands the interval forwards and/or backwards in time. Effectively,
            it subtracts the given number of months/years from $(D begin).

            Params:
                years         = The number of years to expand the interval by.
                months        = The number of months to expand the interval by.
                allowOverflow = Whether the days should be allowed to overflow
                                on $(D begin), causing its month to increment.

            Throws:
                $(LREF DateTimeException) if this interval is empty or if the
                resulting interval would be invalid.

            Example:
--------------------
auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

interval1.expand(2);
assert(interval1 == PosInfInterval!Date(Date(1994, 1, 2)));

interval2.expand(-2);
assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2)));
--------------------
          +/
        void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
            if(isIntegral!T)
        {
            auto begin = _begin;

            begin.add!"years"(-years, allowOverflow);
            begin.add!"months"(-months, allowOverflow);

            _begin = begin;

            return;
        }
    }


    /++
        Returns a range which iterates forward over the interval, starting
        at $(D begin), using $(D_PARAM func) to generate each successive time
        point.

        The range's $(D front) is the interval's $(D begin). $(D_PARAM func) is
        used to generate the next $(D front) when $(D popFront) is called. If
        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
        before the range is returned (so that $(D front) is a time point which
        $(D_PARAM func) would generate).

        If $(D_PARAM func) ever generates a time point less than or equal to the
        current $(D front) of the range, then a $(LREF DateTimeException) will be
        thrown.

        There are helper functions in this module which generate common
        delegates to pass to $(D fwdRange). Their documentation starts with
        "Range-generating function," to make them easily searchable.

        Params:
            func     = The function used to generate the time points of the
                       range over the interval.
            popFirst = Whether $(D popFront) should be called on the range
                       before returning it.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Warning:
            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
            would be a function pointer to a pure function, but forcing
            $(D_PARAM func) to be pure is far too restrictive to be useful, and
            in order to have the ease of use of having functions which generate
            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
            delegate.

            If $(D_PARAM func) retains state which changes as it is called, then
            some algorithms will not work correctly, because the range's
            $(D save) will have failed to have really saved the range's state.
            To avoid such bugs, don't pass a delegate which is
            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
            same time point with two different calls, it must return the same
            result both times.

            Of course, none of the functions in this module have this problem,
            so it's only relevant for custom delegates.

        Example:
--------------------
auto interval = PosInfInterval!Date(Date(2010, 9, 1));
auto func = delegate (in Date date) //For iterating over even-numbered days.
            {
                if((date.day & 1) == 0)
                    return date + dur!"days"(2);

                return date + dur!"days"(1);
            };
auto range = interval.fwdRange(func);

//An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).
assert(range.front == Date(2010, 9, 1));

range.popFront();
assert(range.front == Date(2010, 9, 2));

range.popFront();
assert(range.front == Date(2010, 9, 4));

range.popFront();
assert(range.front == Date(2010, 9, 6));

range.popFront();
assert(range.front == Date(2010, 9, 8));

range.popFront();
assert(!range.empty);
--------------------
      +/
    PosInfIntervalRange!(TP) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
    {
        auto range = PosInfIntervalRange!(TP)(this, func);

        if(popFirst == PopFirst.yes)
            range.popFront();

        return range;
    }


    /+
        Converts this interval to a string.
      +/
    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
    //have versions of toString() with extra modifiers, so we define one version
    //with modifiers and one without.
    string toString()
    {
        return _toStringImpl();
    }


    /++
        Converts this interval to a string.
      +/
    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
    //have versions of toString() with extra modifiers, so we define one version
    //with modifiers and one without.
    string toString() const nothrow
    {
        return _toStringImpl();
    }

private:

    /+
        Since we have two versions of toString(), we have _toStringImpl()
        so that they can share implementations.
      +/
    string _toStringImpl() const nothrow
    {
        import std.format : format;
        try
            return format("[%s - ∞)", _begin);
        catch(Exception e)
            assert(0, "format() threw.");
    }


    TP _begin;
}

//Test PosInfInterval's constructor.
unittest
{
    PosInfInterval!Date(Date.init);
    PosInfInterval!TimeOfDay(TimeOfDay.init);
    PosInfInterval!DateTime(DateTime.init);
    PosInfInterval!SysTime(SysTime(0));

    //Verify Examples.
    auto interval = PosInfInterval!Date(Date(1996, 1, 2));
}

//Test PosInfInterval's begin.
unittest
{
    assert(PosInfInterval!Date(Date(1, 1, 1)).begin == Date(1, 1, 1));
    assert(PosInfInterval!Date(Date(2010, 1, 1)).begin == Date(2010, 1, 1));
    assert(PosInfInterval!Date(Date(1997, 12, 31)).begin == Date(1997, 12, 31));

    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    assert(cPosInfInterval.begin != Date.init);
    assert(iPosInfInterval.begin != Date.init);

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2));
}

//Test PosInfInterval's empty.
unittest
{
    assert(!PosInfInterval!Date(Date(2010, 1, 1)).empty);
    assert(!PosInfInterval!TimeOfDay(TimeOfDay(0, 30, 0)).empty);
    assert(!PosInfInterval!DateTime(DateTime(2010, 1, 1, 0, 30, 0)).empty);
    assert(!PosInfInterval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0))).empty);

    const cPosInfInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iPosInfInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    assert(!cPosInfInterval.empty);
    assert(!iPosInfInterval.empty);

    //Verify Examples.
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).empty);
}

//Test PosInfInterval's contains(time point).
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    assert(!posInfInterval.contains(Date(2009, 7, 4)));
    assert(!posInfInterval.contains(Date(2010, 7, 3)));
    assert(posInfInterval.contains(Date(2010, 7, 4)));
    assert(posInfInterval.contains(Date(2010, 7, 5)));
    assert(posInfInterval.contains(Date(2011, 7, 1)));
    assert(posInfInterval.contains(Date(2012, 1, 6)));
    assert(posInfInterval.contains(Date(2012, 1, 7)));
    assert(posInfInterval.contains(Date(2012, 1, 8)));
    assert(posInfInterval.contains(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    assert(posInfInterval.contains(cdate));
    assert(cPosInfInterval.contains(cdate));
    assert(iPosInfInterval.contains(cdate));

    //Verify Examples.
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(1994, 12, 24)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(2000, 1, 5)));
}

//Test PosInfInterval's contains(Interval).
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.contains(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(posInfInterval.contains(posInfInterval));
    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(posInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(posInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(PosInfInterval!Date(Date(2010, 7, 3)).contains(posInfInterval));
    assert(PosInfInterval!Date(Date(2010, 7, 4)).contains(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 5)).contains(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 6)).contains(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 7)).contains(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 8)).contains(posInfInterval));

    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(posInfInterval.contains(interval));
    assert(posInfInterval.contains(cInterval));
    assert(posInfInterval.contains(iInterval));
    assert(posInfInterval.contains(posInfInterval));
    assert(posInfInterval.contains(cPosInfInterval));
    assert(posInfInterval.contains(iPosInfInterval));
    assert(!posInfInterval.contains(negInfInterval));
    assert(!posInfInterval.contains(cNegInfInterval));
    assert(!posInfInterval.contains(iNegInfInterval));
    assert(cPosInfInterval.contains(interval));
    assert(cPosInfInterval.contains(cInterval));
    assert(cPosInfInterval.contains(iInterval));
    assert(cPosInfInterval.contains(posInfInterval));
    assert(cPosInfInterval.contains(cPosInfInterval));
    assert(cPosInfInterval.contains(iPosInfInterval));
    assert(!cPosInfInterval.contains(negInfInterval));
    assert(!cPosInfInterval.contains(cNegInfInterval));
    assert(!cPosInfInterval.contains(iNegInfInterval));
    assert(iPosInfInterval.contains(interval));
    assert(iPosInfInterval.contains(cInterval));
    assert(iPosInfInterval.contains(iInterval));
    assert(iPosInfInterval.contains(posInfInterval));
    assert(iPosInfInterval.contains(cPosInfInterval));
    assert(iPosInfInterval.contains(iPosInfInterval));
    assert(!iPosInfInterval.contains(negInfInterval));
    assert(!iPosInfInterval.contains(cNegInfInterval));
    assert(!iPosInfInterval.contains(iNegInfInterval));

    //Verify Examples.
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(PosInfInterval!Date(Date(1995, 7, 2))));

    assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(NegInfInterval!Date(Date(1996, 5, 4))));
}

//Test PosInfInterval's isBefore(time point).
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    assert(!posInfInterval.isBefore(Date(2009, 7, 3)));
    assert(!posInfInterval.isBefore(Date(2010, 7, 3)));
    assert(!posInfInterval.isBefore(Date(2010, 7, 4)));
    assert(!posInfInterval.isBefore(Date(2010, 7, 5)));
    assert(!posInfInterval.isBefore(Date(2011, 7, 1)));
    assert(!posInfInterval.isBefore(Date(2012, 1, 6)));
    assert(!posInfInterval.isBefore(Date(2012, 1, 7)));
    assert(!posInfInterval.isBefore(Date(2012, 1, 8)));
    assert(!posInfInterval.isBefore(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    assert(!posInfInterval.isBefore(cdate));
    assert(!cPosInfInterval.isBefore(cdate));
    assert(!iPosInfInterval.isBefore(cdate));

    //Verify Examples.
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(1994, 12, 24)));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(2000, 1, 5)));
}

//Test PosInfInterval's isBefore(Interval).
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.isBefore(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!posInfInterval.isBefore(posInfInterval));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!PosInfInterval!Date(Date(2010, 7, 3)).isBefore(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 4)).isBefore(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 5)).isBefore(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 6)).isBefore(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 7)).isBefore(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 8)).isBefore(posInfInterval));

    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!posInfInterval.isBefore(interval));
    assert(!posInfInterval.isBefore(cInterval));
    assert(!posInfInterval.isBefore(iInterval));
    assert(!posInfInterval.isBefore(posInfInterval));
    assert(!posInfInterval.isBefore(cPosInfInterval));
    assert(!posInfInterval.isBefore(iPosInfInterval));
    assert(!posInfInterval.isBefore(negInfInterval));
    assert(!posInfInterval.isBefore(cNegInfInterval));
    assert(!posInfInterval.isBefore(iNegInfInterval));
    assert(!cPosInfInterval.isBefore(interval));
    assert(!cPosInfInterval.isBefore(cInterval));
    assert(!cPosInfInterval.isBefore(iInterval));
    assert(!cPosInfInterval.isBefore(posInfInterval));
    assert(!cPosInfInterval.isBefore(cPosInfInterval));
    assert(!cPosInfInterval.isBefore(iPosInfInterval));
    assert(!cPosInfInterval.isBefore(negInfInterval));
    assert(!cPosInfInterval.isBefore(cNegInfInterval));
    assert(!cPosInfInterval.isBefore(iNegInfInterval));
    assert(!iPosInfInterval.isBefore(interval));
    assert(!iPosInfInterval.isBefore(cInterval));
    assert(!iPosInfInterval.isBefore(iInterval));
    assert(!iPosInfInterval.isBefore(posInfInterval));
    assert(!iPosInfInterval.isBefore(cPosInfInterval));
    assert(!iPosInfInterval.isBefore(iPosInfInterval));
    assert(!iPosInfInterval.isBefore(negInfInterval));
    assert(!iPosInfInterval.isBefore(cNegInfInterval));
    assert(!iPosInfInterval.isBefore(iNegInfInterval));

    //Verify Examples.
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(PosInfInterval!Date(Date(1992, 5, 4))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(PosInfInterval!Date(Date(2013, 3, 7))));

    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(NegInfInterval!Date(Date(1996, 5, 4))));
}

//Test PosInfInterval's isAfter(time point).
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    assert(posInfInterval.isAfter(Date(2009, 7, 3)));
    assert(posInfInterval.isAfter(Date(2010, 7, 3)));
    assert(!posInfInterval.isAfter(Date(2010, 7, 4)));
    assert(!posInfInterval.isAfter(Date(2010, 7, 5)));
    assert(!posInfInterval.isAfter(Date(2011, 7, 1)));
    assert(!posInfInterval.isAfter(Date(2012, 1, 6)));
    assert(!posInfInterval.isAfter(Date(2012, 1, 7)));
    assert(!posInfInterval.isAfter(Date(2012, 1, 8)));
    assert(!posInfInterval.isAfter(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    assert(!posInfInterval.isAfter(cdate));
    assert(!cPosInfInterval.isAfter(cdate));
    assert(!iPosInfInterval.isAfter(cdate));

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(1994, 12, 24)));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(2000, 1, 5)));
}

//Test PosInfInterval's isAfter(Interval).
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.isAfter(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!posInfInterval.isAfter(posInfInterval));
    assert(posInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!PosInfInterval!Date(Date(2010, 7, 3)).isAfter(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 4)).isAfter(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 5)).isAfter(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 6)).isAfter(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 7)).isAfter(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 8)).isAfter(posInfInterval));

    assert(posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!posInfInterval.isAfter(interval));
    assert(!posInfInterval.isAfter(cInterval));
    assert(!posInfInterval.isAfter(iInterval));
    assert(!posInfInterval.isAfter(posInfInterval));
    assert(!posInfInterval.isAfter(cPosInfInterval));
    assert(!posInfInterval.isAfter(iPosInfInterval));
    assert(!posInfInterval.isAfter(negInfInterval));
    assert(!posInfInterval.isAfter(cNegInfInterval));
    assert(!posInfInterval.isAfter(iNegInfInterval));
    assert(!cPosInfInterval.isAfter(interval));
    assert(!cPosInfInterval.isAfter(cInterval));
    assert(!cPosInfInterval.isAfter(iInterval));
    assert(!cPosInfInterval.isAfter(posInfInterval));
    assert(!cPosInfInterval.isAfter(cPosInfInterval));
    assert(!cPosInfInterval.isAfter(iPosInfInterval));
    assert(!cPosInfInterval.isAfter(negInfInterval));
    assert(!cPosInfInterval.isAfter(cNegInfInterval));
    assert(!cPosInfInterval.isAfter(iNegInfInterval));
    assert(!iPosInfInterval.isAfter(interval));
    assert(!iPosInfInterval.isAfter(cInterval));
    assert(!iPosInfInterval.isAfter(iInterval));
    assert(!iPosInfInterval.isAfter(posInfInterval));
    assert(!iPosInfInterval.isAfter(cPosInfInterval));
    assert(!iPosInfInterval.isAfter(iPosInfInterval));
    assert(!iPosInfInterval.isAfter(negInfInterval));
    assert(!iPosInfInterval.isAfter(cNegInfInterval));
    assert(!iPosInfInterval.isAfter(iNegInfInterval));

    //Verify Examples.
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));

    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(PosInfInterval!Date(Date(1990, 1, 7))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(PosInfInterval!Date(Date(1999, 5, 4))));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(NegInfInterval!Date(Date(1996, 1, 2))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(NegInfInterval!Date(Date(2000, 7, 1))));
}

//Test PosInfInterval's intersects().
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.intersects(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(posInfInterval.intersects(posInfInterval));
    assert(!posInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(PosInfInterval!Date(Date(2010, 7, 3)).intersects(posInfInterval));
    assert(PosInfInterval!Date(Date(2010, 7, 4)).intersects(posInfInterval));
    assert(PosInfInterval!Date(Date(2010, 7, 5)).intersects(posInfInterval));
    assert(PosInfInterval!Date(Date(2012, 1, 6)).intersects(posInfInterval));
    assert(PosInfInterval!Date(Date(2012, 1, 7)).intersects(posInfInterval));
    assert(PosInfInterval!Date(Date(2012, 1, 8)).intersects(posInfInterval));

    assert(!posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(posInfInterval.intersects(interval));
    assert(posInfInterval.intersects(cInterval));
    assert(posInfInterval.intersects(iInterval));
    assert(posInfInterval.intersects(posInfInterval));
    assert(posInfInterval.intersects(cPosInfInterval));
    assert(posInfInterval.intersects(iPosInfInterval));
    assert(posInfInterval.intersects(negInfInterval));
    assert(posInfInterval.intersects(cNegInfInterval));
    assert(posInfInterval.intersects(iNegInfInterval));
    assert(cPosInfInterval.intersects(interval));
    assert(cPosInfInterval.intersects(cInterval));
    assert(cPosInfInterval.intersects(iInterval));
    assert(cPosInfInterval.intersects(posInfInterval));
    assert(cPosInfInterval.intersects(cPosInfInterval));
    assert(cPosInfInterval.intersects(iPosInfInterval));
    assert(cPosInfInterval.intersects(negInfInterval));
    assert(cPosInfInterval.intersects(cNegInfInterval));
    assert(cPosInfInterval.intersects(iNegInfInterval));
    assert(iPosInfInterval.intersects(interval));
    assert(iPosInfInterval.intersects(cInterval));
    assert(iPosInfInterval.intersects(iInterval));
    assert(iPosInfInterval.intersects(posInfInterval));
    assert(iPosInfInterval.intersects(cPosInfInterval));
    assert(iPosInfInterval.intersects(iPosInfInterval));
    assert(iPosInfInterval.intersects(negInfInterval));
    assert(iPosInfInterval.intersects(cNegInfInterval));
    assert(iPosInfInterval.intersects(iNegInfInterval));

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(PosInfInterval!Date(Date(1990, 1, 7))));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(PosInfInterval!Date(Date(1999, 5, 4))));

    assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(NegInfInterval!Date(Date(1996, 1, 2))));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(NegInfInterval!Date(Date(2000, 7, 1))));
}

//Test PosInfInterval's intersection().
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(I, J)(in I interval1, in J interval2)
    {
        interval1.intersection(interval2);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));

    assertThrown!DateTimeException(testInterval(posInfInterval, NegInfInterval!Date(Date(2010, 7, 3))));
    assertThrown!DateTimeException(testInterval(posInfInterval, NegInfInterval!Date(Date(2010, 7, 4))));

    assert(posInfInterval.intersection(posInfInterval) ==
                posInfInterval);
    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                Interval!Date(Date(2010, 7, 4), Date(2013, 7, 3)));
    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));
    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
    assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)));
    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)));
    assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
                Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)));

    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 5)));
    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2012, 1, 6)));
    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2012, 1, 7)));
    assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2012, 1, 8)));

    assert(PosInfInterval!Date(Date(2010, 7, 3)).intersection(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2010, 7, 4)).intersection(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2010, 7, 5)).intersection(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 5)));
    assert(PosInfInterval!Date(Date(2012, 1, 6)).intersection(posInfInterval) ==
                PosInfInterval!Date(Date(2012, 1, 6)));
    assert(PosInfInterval!Date(Date(2012, 1, 7)).intersection(posInfInterval) ==
                PosInfInterval!Date(Date(2012, 1, 7)));
    assert(PosInfInterval!Date(Date(2012, 1, 8)).intersection(posInfInterval) ==
                PosInfInterval!Date(Date(2012, 1, 8)));

    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)));
    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 6)));
    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)));
    assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8)));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!posInfInterval.intersection(interval).empty);
    assert(!posInfInterval.intersection(cInterval).empty);
    assert(!posInfInterval.intersection(iInterval).empty);
    assert(!posInfInterval.intersection(posInfInterval).empty);
    assert(!posInfInterval.intersection(cPosInfInterval).empty);
    assert(!posInfInterval.intersection(iPosInfInterval).empty);
    assert(!posInfInterval.intersection(negInfInterval).empty);
    assert(!posInfInterval.intersection(cNegInfInterval).empty);
    assert(!posInfInterval.intersection(iNegInfInterval).empty);
    assert(!cPosInfInterval.intersection(interval).empty);
    assert(!cPosInfInterval.intersection(cInterval).empty);
    assert(!cPosInfInterval.intersection(iInterval).empty);
    assert(!cPosInfInterval.intersection(posInfInterval).empty);
    assert(!cPosInfInterval.intersection(cPosInfInterval).empty);
    assert(!cPosInfInterval.intersection(iPosInfInterval).empty);
    assert(!cPosInfInterval.intersection(negInfInterval).empty);
    assert(!cPosInfInterval.intersection(cNegInfInterval).empty);
    assert(!cPosInfInterval.intersection(iNegInfInterval).empty);
    assert(!iPosInfInterval.intersection(interval).empty);
    assert(!iPosInfInterval.intersection(cInterval).empty);
    assert(!iPosInfInterval.intersection(iInterval).empty);
    assert(!iPosInfInterval.intersection(posInfInterval).empty);
    assert(!iPosInfInterval.intersection(cPosInfInterval).empty);
    assert(!iPosInfInterval.intersection(iPosInfInterval).empty);
    assert(!iPosInfInterval.intersection(negInfInterval).empty);
    assert(!iPosInfInterval.intersection(cNegInfInterval).empty);
    assert(!iPosInfInterval.intersection(iNegInfInterval).empty);

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17)));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1996, 1 , 2)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == PosInfInterval!Date(Date(1999, 1 , 12)));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == Interval!Date(Date(1996, 1 , 2), Date(2013, 1, 12)));
}

//Test PosInfInterval's isAdjacent().
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.isAdjacent(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!posInfInterval.isAdjacent(posInfInterval));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8))));

    assert(!PosInfInterval!Date(Date(2010, 7, 3)).isAdjacent(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 4)).isAdjacent(posInfInterval));
    assert(!PosInfInterval!Date(Date(2010, 7, 5)).isAdjacent(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 6)).isAdjacent(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 7)).isAdjacent(posInfInterval));
    assert(!PosInfInterval!Date(Date(2012, 1, 8)).isAdjacent(posInfInterval));

    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!posInfInterval.isAdjacent(interval));
    assert(!posInfInterval.isAdjacent(cInterval));
    assert(!posInfInterval.isAdjacent(iInterval));
    assert(!posInfInterval.isAdjacent(posInfInterval));
    assert(!posInfInterval.isAdjacent(cPosInfInterval));
    assert(!posInfInterval.isAdjacent(iPosInfInterval));
    assert(!posInfInterval.isAdjacent(negInfInterval));
    assert(!posInfInterval.isAdjacent(cNegInfInterval));
    assert(!posInfInterval.isAdjacent(iNegInfInterval));
    assert(!cPosInfInterval.isAdjacent(interval));
    assert(!cPosInfInterval.isAdjacent(cInterval));
    assert(!cPosInfInterval.isAdjacent(iInterval));
    assert(!cPosInfInterval.isAdjacent(posInfInterval));
    assert(!cPosInfInterval.isAdjacent(cPosInfInterval));
    assert(!cPosInfInterval.isAdjacent(iPosInfInterval));
    assert(!cPosInfInterval.isAdjacent(negInfInterval));
    assert(!cPosInfInterval.isAdjacent(cNegInfInterval));
    assert(!cPosInfInterval.isAdjacent(iNegInfInterval));
    assert(!iPosInfInterval.isAdjacent(interval));
    assert(!iPosInfInterval.isAdjacent(cInterval));
    assert(!iPosInfInterval.isAdjacent(iInterval));
    assert(!iPosInfInterval.isAdjacent(posInfInterval));
    assert(!iPosInfInterval.isAdjacent(cPosInfInterval));
    assert(!iPosInfInterval.isAdjacent(iPosInfInterval));
    assert(!iPosInfInterval.isAdjacent(negInfInterval));
    assert(!iPosInfInterval.isAdjacent(cNegInfInterval));
    assert(!iPosInfInterval.isAdjacent(iNegInfInterval));

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2))));
    assert(!PosInfInterval!Date(Date(1999, 1, 12)).isAdjacent(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(PosInfInterval!Date(Date(1990, 1, 7))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(PosInfInterval!Date(Date(1996, 1, 2))));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(NegInfInterval!Date(Date(1996, 1, 2))));
    assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(NegInfInterval!Date(Date(2000, 7, 1))));
}

//Test PosInfInterval's merge().
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.merge(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));

    assert(posInfInterval.merge(posInfInterval) ==
                posInfInterval);
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 1)));
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    assert(PosInfInterval!Date(Date(2010, 7, 3)).merge(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(PosInfInterval!Date(Date(2010, 7, 4)).merge(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2010, 7, 5)).merge(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2012, 1, 6)).merge(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2012, 1, 7)).merge(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2012, 1, 8)).merge(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 3)))));
    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 4)))));
    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 5)))));
    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 6)))));
    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 7)))));
    static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 8)))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!posInfInterval.merge(interval).empty);
    assert(!posInfInterval.merge(cInterval).empty);
    assert(!posInfInterval.merge(iInterval).empty);
    assert(!posInfInterval.merge(posInfInterval).empty);
    assert(!posInfInterval.merge(cPosInfInterval).empty);
    assert(!posInfInterval.merge(iPosInfInterval).empty);
    static assert(!__traits(compiles, posInfInterval.merge(negInfInterval)));
    static assert(!__traits(compiles, posInfInterval.merge(cNegInfInterval)));
    static assert(!__traits(compiles, posInfInterval.merge(iNegInfInterval)));
    assert(!cPosInfInterval.merge(interval).empty);
    assert(!cPosInfInterval.merge(cInterval).empty);
    assert(!cPosInfInterval.merge(iInterval).empty);
    assert(!cPosInfInterval.merge(posInfInterval).empty);
    assert(!cPosInfInterval.merge(cPosInfInterval).empty);
    assert(!cPosInfInterval.merge(iPosInfInterval).empty);
    static assert(!__traits(compiles, cPosInfInterval.merge(negInfInterval)));
    static assert(!__traits(compiles, cPosInfInterval.merge(cNegInfInterval)));
    static assert(!__traits(compiles, cPosInfInterval.merge(iNegInfInterval)));
    assert(!iPosInfInterval.merge(interval).empty);
    assert(!iPosInfInterval.merge(cInterval).empty);
    assert(!iPosInfInterval.merge(iInterval).empty);
    assert(!iPosInfInterval.merge(posInfInterval).empty);
    assert(!iPosInfInterval.merge(cPosInfInterval).empty);
    assert(!iPosInfInterval.merge(iPosInfInterval).empty);
    static assert(!__traits(compiles, iPosInfInterval.merge(negInfInterval)));
    static assert(!__traits(compiles, iPosInfInterval.merge(cNegInfInterval)));
    static assert(!__traits(compiles, iPosInfInterval.merge(iNegInfInterval)));

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == PosInfInterval!Date(Date(1990, 7 , 6)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == PosInfInterval!Date(Date(1996, 1 , 2)));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(PosInfInterval!Date(Date(1999, 1, 12))) == PosInfInterval!Date(Date(1996, 1 , 2)));
}

//Test PosInfInterval's span().
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval)
    {
        posInfInterval.span(interval);
    }

    assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(posInfInterval.span(posInfInterval) ==
                posInfInterval);
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 1)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 1)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 3))) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 4))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 5))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 6))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 7))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 8))) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    assert(PosInfInterval!Date(Date(2010, 7, 3)).span(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 3)));
    assert(PosInfInterval!Date(Date(2010, 7, 4)).span(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2010, 7, 5)).span(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2012, 1, 6)).span(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2012, 1, 7)).span(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));
    assert(PosInfInterval!Date(Date(2012, 1, 8)).span(posInfInterval) ==
                PosInfInterval!Date(Date(2010, 7, 4)));

    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 3)))));
    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 4)))));
    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 5)))));
    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 6)))));
    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 7)))));
    static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 8)))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!posInfInterval.span(interval).empty);
    assert(!posInfInterval.span(cInterval).empty);
    assert(!posInfInterval.span(iInterval).empty);
    assert(!posInfInterval.span(posInfInterval).empty);
    assert(!posInfInterval.span(cPosInfInterval).empty);
    assert(!posInfInterval.span(iPosInfInterval).empty);
    static assert(!__traits(compiles, posInfInterval.span(negInfInterval)));
    static assert(!__traits(compiles, posInfInterval.span(cNegInfInterval)));
    static assert(!__traits(compiles, posInfInterval.span(iNegInfInterval)));
    assert(!cPosInfInterval.span(interval).empty);
    assert(!cPosInfInterval.span(cInterval).empty);
    assert(!cPosInfInterval.span(iInterval).empty);
    assert(!cPosInfInterval.span(posInfInterval).empty);
    assert(!cPosInfInterval.span(cPosInfInterval).empty);
    assert(!cPosInfInterval.span(iPosInfInterval).empty);
    static assert(!__traits(compiles, cPosInfInterval.span(negInfInterval)));
    static assert(!__traits(compiles, cPosInfInterval.span(cNegInfInterval)));
    static assert(!__traits(compiles, cPosInfInterval.span(iNegInfInterval)));
    assert(!iPosInfInterval.span(interval).empty);
    assert(!iPosInfInterval.span(cInterval).empty);
    assert(!iPosInfInterval.span(iInterval).empty);
    assert(!iPosInfInterval.span(posInfInterval).empty);
    assert(!iPosInfInterval.span(cPosInfInterval).empty);
    assert(!iPosInfInterval.span(iPosInfInterval).empty);
    static assert(!__traits(compiles, iPosInfInterval.span(negInfInterval)));
    static assert(!__traits(compiles, iPosInfInterval.span(cNegInfInterval)));
    static assert(!__traits(compiles, iPosInfInterval.span(iNegInfInterval)));

    //Verify Examples.
    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(500, 8, 9), Date(1602, 1, 31))) == PosInfInterval!Date(Date(500, 8, 9)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == PosInfInterval!Date(Date(1990, 7 , 6)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == PosInfInterval!Date(Date(1996, 1 , 2)));

    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(PosInfInterval!Date(Date(1990, 7, 6))) == PosInfInterval!Date(Date(1990, 7 , 6)));
    assert(PosInfInterval!Date(Date(1996, 1, 2)).span(PosInfInterval!Date(Date(1999, 1, 12))) == PosInfInterval!Date(Date(1996, 1 , 2)));
}

//Test PosInfInterval's shift().
unittest
{
    auto interval = PosInfInterval!Date(Date(2010, 7, 4));

    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
    {
        interval.shift(duration);
        assert(interval == expected);
    }

    testInterval(interval, dur!"days"(22), PosInfInterval!Date(Date(2010, 7, 26)));
    testInterval(interval, dur!"days"(-22), PosInfInterval!Date(Date(2010, 6, 12)));

    const cInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iInterval = PosInfInterval!Date(Date(2010, 7, 4));
    static assert(!__traits(compiles, cInterval.shift(dur!"days"(5))));
    static assert(!__traits(compiles, iInterval.shift(dur!"days"(5))));

    //Verify Examples.
    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

    interval1.shift(dur!"days"(50));
    assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21)));

    interval2.shift(dur!"days"(-50));
    assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13)));
}

//Test PosInfInterval's shift(int, int, AllowDayOverflow).
unittest
{
    {
        auto interval = PosInfInterval!Date(Date(2010, 7, 4));

        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
        {
            interval.shift(years, months, allow);
            assert(interval == expected);
        }

        testInterval(interval, 5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2015, 7, 4)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2005, 7, 4)));

        auto interval2 = PosInfInterval!Date(Date(2000, 1, 29));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2001, 3, 1)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2000, 12, 29)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1998, 12, 29)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1999, 3, 1)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(2001, 2, 28)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(2000, 12, 29)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(1998, 12, 29)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(1999, 2, 28)));
    }

    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    static assert(!__traits(compiles, cPosInfInterval.shift(1)));
    static assert(!__traits(compiles, iPosInfInterval.shift(1)));

    //Verify Examples.
    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

    interval1.shift(2);
    assert(interval1 == PosInfInterval!Date(Date(1998, 1, 2)));

    interval2.shift(-2);
    assert(interval2 == PosInfInterval!Date(Date(1994, 1, 2)));
}

//Test PosInfInterval's expand().
unittest
{
    auto interval = PosInfInterval!Date(Date(2000, 7, 4));

    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
    {
        interval.expand(duration);
        assert(interval == expected);
    }

    testInterval(interval, dur!"days"(22), PosInfInterval!Date(Date(2000, 6, 12)));
    testInterval(interval, dur!"days"(-22), PosInfInterval!Date(Date(2000, 7, 26)));

    const cInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iInterval = PosInfInterval!Date(Date(2010, 7, 4));
    static assert(!__traits(compiles, cInterval.expand(dur!"days"(5))));
    static assert(!__traits(compiles, iInterval.expand(dur!"days"(5))));

    //Verify Examples.
    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

    interval1.expand(dur!"days"(2));
    assert(interval1 == PosInfInterval!Date(Date(1995, 12, 31)));

    interval2.expand(dur!"days"(-2));
    assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4)));
}

//Test PosInfInterval's expand(int, int, AllowDayOverflow).
unittest
{
    {
        auto interval = PosInfInterval!Date(Date(2000, 7, 4));

        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
        {
            interval.expand(years, months, allow);
            assert(interval == expected);
        }

        testInterval(interval, 5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(1995, 7, 4)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2005, 7, 4)));

        auto interval2 = PosInfInterval!Date(Date(2000, 1, 29));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1998, 12, 29)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1999, 3, 1)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2001, 3, 1)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2000, 12, 29)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(1998, 12, 29)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(1999, 2, 28)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(2001, 2, 28)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(2000, 12, 29)));
    }

    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    static assert(!__traits(compiles, cPosInfInterval.expand(1)));
    static assert(!__traits(compiles, iPosInfInterval.expand(1)));

    //Verify Examples.
    auto interval1 = PosInfInterval!Date(Date(1996, 1, 2));
    auto interval2 = PosInfInterval!Date(Date(1996, 1, 2));

    interval1.expand(2);
    assert(interval1 == PosInfInterval!Date(Date(1994, 1, 2)));

    interval2.expand(-2);
    assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2)));
}

//Test PosInfInterval's fwdRange().
unittest
{
    auto posInfInterval = PosInfInterval!Date(Date(2010, 9, 19));

    static void testInterval(PosInfInterval!Date posInfInterval)
    {
        posInfInterval.fwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).popFront();
    }

    assertThrown!DateTimeException(testInterval(posInfInterval));

    assert(PosInfInterval!Date(Date(2010, 9, 12)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).front ==
                Date(2010, 9, 12));

    assert(PosInfInterval!Date(Date(2010, 9, 12)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).front ==
                Date(2010, 9, 17));

    //Verify Examples.
    auto interval = PosInfInterval!Date(Date(2010, 9, 1));
    auto func = delegate (in Date date)
                {
                    if((date.day & 1) == 0)
                        return date + dur!"days"(2);

                    return date + dur!"days"(1);
                };
    auto range = interval.fwdRange(func);

    assert(range.front == Date(2010, 9, 1)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2).

    range.popFront();
    assert(range.front == Date(2010, 9, 2));

    range.popFront();
    assert(range.front == Date(2010, 9, 4));

    range.popFront();
    assert(range.front == Date(2010, 9, 6));

    range.popFront();
    assert(range.front == Date(2010, 9, 8));

    range.popFront();
    assert(!range.empty);

    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    assert(!cPosInfInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
    assert(!iPosInfInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty);
}

//Test PosInfInterval's toString().
unittest
{
    assert(PosInfInterval!Date(Date(2010, 7, 4)).toString() == "[2010-Jul-04 - ∞)");

    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    assert(cPosInfInterval.toString());
    assert(iPosInfInterval.toString());
}


/++
    Represents an interval of time which has negative infinity as its starting
    point.

    Any ranges which iterate over a $(D NegInfInterval) are infinite. So, the
    main purpose of using $(D NegInfInterval) is to create an infinite range
    which starts at negative infinity and goes to a fixed end point.
    Iterate over it in reverse.
  +/
struct NegInfInterval(TP)
{
public:

    /++
        Params:
            end = The time point which ends the interval.

        Example:
--------------------
auto interval = PosInfInterval!Date(Date(1996, 1, 2));
--------------------
      +/
    this(in TP end) pure nothrow
    {
        _end = cast(TP)end;
    }


    /++
        Params:
            rhs = The $(D NegInfInterval) to assign to this one.
      +/
    ref NegInfInterval opAssign(const ref NegInfInterval rhs) pure nothrow
    {
        _end = cast(TP)rhs._end;
        return this;
    }


    /++
        Params:
            rhs = The $(D NegInfInterval) to assign to this one.
      +/
    ref NegInfInterval opAssign(NegInfInterval rhs) pure nothrow
    {
        _end = cast(TP)rhs._end;
        return this;
    }


    /++
        The end point of the interval. It is excluded from the interval.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).end == Date(2012, 3, 1));
--------------------
      +/
    @property TP end() const pure nothrow
    {
        return cast(TP)_end;
    }


    /++
        The end point of the interval. It is excluded from the interval.

        Params:
            timePoint = The time point to set end to.
      +/
    @property void end(TP timePoint) pure nothrow
    {
        _end = timePoint;
    }


    /++
        Whether the interval's length is 0. Always returns false.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(1996, 1, 2)).empty);
--------------------
      +/
    @property bool empty() const pure nothrow
    {
        return false;
    }


    /++
        Whether the given time point is within this interval.

        Params:
            timePoint = The time point to check for inclusion in this interval.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(1994, 12, 24)));
assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2000, 1, 5)));
assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2012, 3, 1)));
--------------------
      +/
    bool contains(TP timePoint) const pure nothrow
    {
        return timePoint < _end;
    }


    /++
        Whether the given interval is completely within this interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(
            Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));
--------------------
      +/
    bool contains(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return interval._end <= _end;
    }


    /++
        Whether the given interval is completely within this interval.

        Always returns false because an interval beginning at negative
        infinity can never contain an interval going to positive infinity.

        Params:
            interval = The interval to check for inclusion in this interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(
            PosInfInterval!Date(Date(1999, 5, 4))));
--------------------
      +/
    bool contains(in PosInfInterval!TP interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether the given interval is completely within this interval.

        Params:
            interval = The interval to check for inclusion in this interval.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(
            NegInfInterval!Date(Date(1996, 5, 4))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(
            NegInfInterval!Date(Date(2013, 7, 9))));
--------------------
      +/
    bool contains(in NegInfInterval interval) const pure nothrow
    {
        return interval._end <= _end;
    }


    /++
        Whether this interval is before the given time point.

        Params:
            timePoint = The time point to check whether this interval is
                        before it.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(1994, 12, 24)));
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2000, 1, 5)));
assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2012, 3, 1)));
--------------------
      +/
    bool isBefore(in TP timePoint) const pure nothrow
    {
        return timePoint >= _end;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect it.

        Params:
            interval = The interval to check for against this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
--------------------
      +/
    bool isBefore(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return _end <= interval._begin;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect it.

        Params:
            interval = The interval to check for against this interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            PosInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool isBefore(in PosInfInterval!TP interval) const pure nothrow
    {
        return _end <= interval._begin;
    }


    /++
        Whether this interval is before the given interval and does not
        intersect it.

        Always returns false because an interval beginning at negative
        infinity can never be before another interval beginning at negative
        infinity.

        Params:
            interval = The interval to check for against this interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            NegInfInterval!Date(Date(1996, 5, 4))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(
            NegInfInterval!Date(Date(2013, 7, 9))));
--------------------
      +/
    bool isBefore(in NegInfInterval interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is after the given time point.

        Always returns false because an interval beginning at negative infinity
        can never be after any time point.

        Params:
            timePoint = The time point to check whether this interval is after
                        it.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(1994, 12, 24)));
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2000, 1, 5)));
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2012, 3, 1)));
--------------------
      +/
    bool isAfter(in TP timePoint) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is after the given interval and does not
        intersect it.

        Always returns false (unless the given interval is empty) because an
        interval beginning at negative infinity can never be after any other
        interval.

        Params:
            interval = The interval to check against this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
--------------------
      +/
    bool isAfter(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return false;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Always returns false because an interval beginning at negative infinity
        can never be after any other interval.

        Params:
            interval = The interval to check against this interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            PosInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool isAfter(in PosInfInterval!TP interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether this interval is after the given interval and does not intersect
        it.

        Always returns false because an interval beginning at negative infinity
        can never be after any other interval.

        Params:
            interval = The interval to check against this interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            NegInfInterval!Date(Date(1996, 5, 4))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(
            NegInfInterval!Date(Date(2013, 7, 9))));
--------------------
      +/
    bool isAfter(in NegInfInterval interval) const pure nothrow
    {
        return false;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
--------------------
      +/
    bool intersects(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return interval._begin < _end;
    }


    /++
        Whether the given interval overlaps this interval.

        Params:
            interval = The interval to check for intersection with this
                       interval.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            PosInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool intersects(in PosInfInterval!TP interval) const pure nothrow
    {
        return interval._begin < _end;
    }


    /++
        Whether the given interval overlaps this interval.

        Always returns true because two intervals beginning at negative infinity
        always overlap.

        Params:
            interval = The interval to check for intersection with this interval.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            NegInfInterval!Date(Date(1996, 5, 4))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(
            NegInfInterval!Date(Date(2013, 7, 9))));
--------------------
      +/
    bool intersects(in NegInfInterval!TP interval) const pure nothrow
    {
        return true;
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect or if
            the given interval is empty.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       Interval!Date(Date(1990, 7 , 6), Date(2000, 8, 2)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
            Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) ==
       Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
--------------------
      +/
    Interval!TP intersection(in Interval!TP interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        auto end = _end < interval._end ? _end : interval._end;

        return Interval!TP(interval._begin, end);
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
            PosInfInterval!Date(Date(1990, 7, 6))) ==
       Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
            PosInfInterval!Date(Date(1999, 1, 12))) ==
       Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));
--------------------
      +/
    Interval!TP intersection(in PosInfInterval!TP interval) const
    {
        import std.format : format;

        enforce(this.intersects(interval), new DateTimeException(format("%s and %s do not intersect.", this, interval)));

        return Interval!TP(interval._begin, _end);
    }


    /++
        Returns the intersection of two intervals

        Params:
            interval = The interval to intersect with this interval.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
            NegInfInterval!Date(Date(1999, 7, 6))) ==
       NegInfInterval!Date(Date(1999, 7 , 6)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));
--------------------
      +/
    NegInfInterval intersection(in NegInfInterval interval) const nothrow
    {
        return NegInfInterval(_end < interval._end ? _end : interval._end);
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(2012, 3, 1), Date(2019, 2, 2))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));
--------------------
      +/
    bool isAdjacent(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return interval._begin == _end;
    }


    /++
        Whether the given interval is adjacent to this interval.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            PosInfInterval!Date(Date(1999, 5, 4))));

assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            PosInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool isAdjacent(in PosInfInterval!TP interval) const pure nothrow
    {
        return interval._begin == _end;
    }


    /++
        Whether the given interval is adjacent to this interval.

        Always returns false because two intervals beginning at negative
        infinity can never be adjacent to one another.

        Params:
            interval = The interval to check whether its adjecent to this
                       interval.

        Example:
--------------------
assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            NegInfInterval!Date(Date(1996, 5, 4))));

assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(
            NegInfInterval!Date(Date(2012, 3, 1))));
--------------------
      +/
    bool isAdjacent(in NegInfInterval interval) const pure nothrow
    {
        return false;
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Throws:
            $(LREF DateTimeException) if the two intervals do not intersect and are
            not adjacent or if the given interval is empty.

        Note:
            There is no overload for $(D merge) which takes a
            $(D PosInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
            Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) ==
       NegInfInterval!Date(Date(2015, 9 , 2)));
--------------------
      +/
    NegInfInterval merge(in Interval!TP interval) const
    {
        import std.format : format;

        enforce(this.isAdjacent(interval) || this.intersects(interval),
                new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval)));

        return NegInfInterval(_end > interval._end ? _end : interval._end);
    }


    /++
        Returns the union of two intervals

        Params:
            interval = The interval to merge with this interval.

        Note:
            There is no overload for $(D merge) which takes a
            $(D PosInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
            NegInfInterval!Date(Date(1999, 7, 6))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       NegInfInterval!Date(Date(2013, 1 , 12)));
--------------------
      +/
    NegInfInterval merge(in NegInfInterval interval) const pure nothrow
    {
        return NegInfInterval(_end > interval._end ? _end : interval._end);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this
                       interval.

        Throws:
            $(LREF DateTimeException) if the given interval is empty.

        Note:
            There is no overload for $(D span) which takes a
            $(D PosInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
            Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
            Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) ==
       NegInfInterval!Date(Date(2015, 9 , 2)));

assert(NegInfInterval!Date(Date(1600, 1, 7)).span(
            Interval!Date(Date(2012, 3, 11), Date(2017, 7, 1))) ==
       NegInfInterval!Date(Date(2017, 7 , 1)));
--------------------
      +/
    NegInfInterval span(in Interval!TP interval) const pure
    {
        interval._enforceNotEmpty();

        return NegInfInterval(_end > interval._end ? _end : interval._end);
    }


    /++
        Returns an interval that covers from the earliest time point of two
        intervals up to (but not including) the latest time point of two
        intervals.

        Params:
            interval = The interval to create a span together with this
                       interval.

        Note:
            There is no overload for $(D span) which takes a
            $(D PosInfInterval), because an interval
            going from negative infinity to positive infinity
            is not possible.

        Example:
--------------------
assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
            NegInfInterval!Date(Date(1999, 7, 6))) ==
       NegInfInterval!Date(Date(2012, 3 , 1)));

assert(NegInfInterval!Date(Date(2012, 3, 1)).span(
            NegInfInterval!Date(Date(2013, 1, 12))) ==
       NegInfInterval!Date(Date(2013, 1 , 12)));
--------------------
      +/
    NegInfInterval span(in NegInfInterval interval) const pure nothrow
    {
        return NegInfInterval(_end > interval._end ? _end : interval._end);
    }


    /++
        Shifts the $(D end) of this interval forward or backwards in time by the
        given duration (a positive duration shifts the interval forward; a
        negative duration shifts it backward). Effectively, it does
        $(D end += duration).

        Params:
            duration = The duration to shift the interval by.

        Example:
--------------------
auto interval1 = NegInfInterval!Date(Date(2012, 4, 5));
auto interval2 = NegInfInterval!Date(Date(2012, 4, 5));

interval1.shift(dur!"days"(50));
assert(interval1 == NegInfInterval!Date(Date(2012, 5, 25)));

interval2.shift(dur!"days"(-50));
assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15)));
--------------------
      +/
    void shift(D)(D duration) pure nothrow
        if(__traits(compiles, end + duration))
    {
        _end += duration;
    }


    static if(__traits(compiles, end.add!"months"(1)) &&
              __traits(compiles, end.add!"years"(1)))
    {
        /++
            Shifts the $(D end) of this interval forward or backwards in time by
            the given number of years and/or months (a positive number of years
            and months shifts the interval forward; a negative number shifts it
            backward). It adds the years the given years and months to end. It
            effectively calls $(D add!"years"()) and then $(D add!"months"())
            on end with the given number of years and months.

            Params:
                years         = The number of years to shift the interval by.
                months        = The number of months to shift the interval by.
                allowOverflow = Whether the days should be allowed to overflow
                                on $(D end), causing its month to increment.

            Throws:
                $(LREF DateTimeException) if empty is true or if the resulting
                interval would be invalid.

            Example:
--------------------
auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));

interval1.shift(2);
assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));

interval2.shift(-2);
assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
--------------------
          +/
        void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
            if(isIntegral!T)
        {
            auto end = _end;

            end.add!"years"(years, allowOverflow);
            end.add!"months"(months, allowOverflow);

            _end = end;
        }
    }


    /++
        Expands the interval forwards in time. Effectively, it does
        $(D end += duration).

        Params:
            duration = The duration to expand the interval by.

        Example:
--------------------
auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));

interval1.expand(dur!"days"(2));
assert(interval1 == NegInfInterval!Date(Date(2012, 3, 3)));

interval2.expand(dur!"days"(-2));
assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28)));
--------------------
      +/
    void expand(D)(D duration) pure nothrow
        if(__traits(compiles, end + duration))
    {
        _end += duration;
    }


    static if(__traits(compiles, end.add!"months"(1)) &&
              __traits(compiles, end.add!"years"(1)))
    {
        /++
            Expands the interval forwards and/or backwards in time. Effectively,
            it adds the given number of months/years to end.

            Params:
                years         = The number of years to expand the interval by.
                months        = The number of months to expand the interval by.
                allowOverflow = Whether the days should be allowed to overflow
                                on $(D end), causing their month to increment.

            Throws:
                $(LREF DateTimeException) if empty is true or if the resulting
                interval would be invalid.

            Example:
--------------------
auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));

interval1.expand(2);
assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));

interval2.expand(-2);
assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
--------------------
          +/
        void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes)
            if(isIntegral!T)
        {
            auto end = _end;

            end.add!"years"(years, allowOverflow);
            end.add!"months"(months, allowOverflow);

            _end = end;

            return;
        }
    }


    /++
        Returns a range which iterates backwards over the interval, starting
        at $(D end), using $(D_PARAM func) to generate each successive time
        point.

        The range's $(D front) is the interval's $(D end). $(D_PARAM func) is
        used to generate the next $(D front) when $(D popFront) is called. If
        $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called
        before the range is returned (so that $(D front) is a time point which
        $(D_PARAM func) would generate).

        If $(D_PARAM func) ever generates a time point greater than or equal to
        the current $(D front) of the range, then a $(LREF DateTimeException) will
        be thrown.

        There are helper functions in this module which generate common
        delegates to pass to $(D bwdRange). Their documentation starts with
        "Range-generating function," to make them easily searchable.

        Params:
            func     = The function used to generate the time points of the
                       range over the interval.
            popFirst = Whether $(D popFront) should be called on the range
                       before returning it.

        Throws:
            $(LREF DateTimeException) if this interval is empty.

        Warning:
            $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func)
            would be a function pointer to a pure function, but forcing
            $(D_PARAM func) to be pure is far too restrictive to be useful, and
            in order to have the ease of use of having functions which generate
            functions to pass to $(D fwdRange), $(D_PARAM func) must be a
            delegate.

            If $(D_PARAM func) retains state which changes as it is called, then
            some algorithms will not work correctly, because the range's
            $(D save) will have failed to have really saved the range's state.
            To avoid such bugs, don't pass a delegate which is
            not logically pure to $(D fwdRange). If $(D_PARAM func) is given the
            same time point with two different calls, it must return the same
            result both times.

            Of course, none of the functions in this module have this problem,
            so it's only relevant for custom delegates.

        Example:
--------------------
auto interval = NegInfInterval!Date(Date(2010, 9, 9));
auto func = delegate (in Date date) //For iterating over even-numbered days.
            {
                if((date.day & 1) == 0)
                    return date - dur!"days"(2);

                return date - dur!"days"(1);
            };
auto range = interval.bwdRange(func);

assert(range.front == Date(2010, 9, 9)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).

range.popFront();
assert(range.front == Date(2010, 9, 8));

range.popFront();
assert(range.front == Date(2010, 9, 6));

range.popFront();
assert(range.front == Date(2010, 9, 4));

range.popFront();
assert(range.front == Date(2010, 9, 2));

range.popFront();
assert(!range.empty);
--------------------
      +/
    NegInfIntervalRange!(TP) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const
    {
        auto range = NegInfIntervalRange!(TP)(this, func);

        if(popFirst == PopFirst.yes)
            range.popFront();

        return range;
    }


    /+
        Converts this interval to a string.
      +/
    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
    //have versions of toString() with extra modifiers, so we define one version
    //with modifiers and one without.
    string toString()
    {
        return _toStringImpl();
    }


    /++
        Converts this interval to a string.
      +/
    //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't
    //have versions of toString() with extra modifiers, so we define one version
    //with modifiers and one without.
    string toString() const nothrow
    {
        return _toStringImpl();
    }

private:

    /+
        Since we have two versions of toString(), we have _toStringImpl()
        so that they can share implementations.
      +/
    string _toStringImpl() const nothrow
    {
        import std.format : format;
        try
            return format("[-∞ - %s)", _end);
        catch(Exception e)
            assert(0, "format() threw.");
    }


    TP _end;
}

//Test NegInfInterval's constructor.
unittest
{
    NegInfInterval!Date(Date.init);
    NegInfInterval!TimeOfDay(TimeOfDay.init);
    NegInfInterval!DateTime(DateTime.init);
    NegInfInterval!SysTime(SysTime(0));
}

//Test NegInfInterval's end.
unittest
{
    assert(NegInfInterval!Date(Date(2010, 1, 1)).end == Date(2010, 1, 1));
    assert(NegInfInterval!Date(Date(2010, 1, 1)).end == Date(2010, 1, 1));
    assert(NegInfInterval!Date(Date(1998, 1, 1)).end == Date(1998, 1, 1));

    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(cNegInfInterval.end != Date.init);
    assert(iNegInfInterval.end != Date.init);

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).end == Date(2012, 3, 1));
}

//Test NegInfInterval's empty.
unittest
{
    assert(!NegInfInterval!Date(Date(2010, 1, 1)).empty);
    assert(!NegInfInterval!TimeOfDay(TimeOfDay(0, 30, 0)).empty);
    assert(!NegInfInterval!DateTime(DateTime(2010, 1, 1, 0, 30, 0)).empty);
    assert(!NegInfInterval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0))).empty);

    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!cNegInfInterval.empty);
    assert(!iNegInfInterval.empty);

    //Verify Examples.
    assert(!NegInfInterval!Date(Date(1996, 1, 2)).empty);
}

//Test NegInfInterval's contains(time point).
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    assert(negInfInterval.contains(Date(2009, 7, 4)));
    assert(negInfInterval.contains(Date(2010, 7, 3)));
    assert(negInfInterval.contains(Date(2010, 7, 4)));
    assert(negInfInterval.contains(Date(2010, 7, 5)));
    assert(negInfInterval.contains(Date(2011, 7, 1)));
    assert(negInfInterval.contains(Date(2012, 1, 6)));
    assert(!negInfInterval.contains(Date(2012, 1, 7)));
    assert(!negInfInterval.contains(Date(2012, 1, 8)));
    assert(!negInfInterval.contains(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(negInfInterval.contains(cdate));
    assert(cNegInfInterval.contains(cdate));
    assert(iNegInfInterval.contains(cdate));

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(1994, 12, 24)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2000, 1, 5)));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2012, 3, 1)));
}

//Test NegInfInterval's contains(Interval).
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
    {
        negInfInterval.contains(interval);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(negInfInterval.contains(negInfInterval));
    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!negInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(negInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 8))));

    assert(!NegInfInterval!Date(Date(2010, 7, 3)).contains(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 4)).contains(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 5)).contains(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 6)).contains(negInfInterval));
    assert(NegInfInterval!Date(Date(2012, 1, 7)).contains(negInfInterval));
    assert(NegInfInterval!Date(Date(2012, 1, 8)).contains(negInfInterval));

    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(negInfInterval.contains(interval));
    assert(negInfInterval.contains(cInterval));
    assert(negInfInterval.contains(iInterval));
    assert(!negInfInterval.contains(posInfInterval));
    assert(!negInfInterval.contains(cPosInfInterval));
    assert(!negInfInterval.contains(iPosInfInterval));
    assert(negInfInterval.contains(negInfInterval));
    assert(negInfInterval.contains(cNegInfInterval));
    assert(negInfInterval.contains(iNegInfInterval));
    assert(cNegInfInterval.contains(interval));
    assert(cNegInfInterval.contains(cInterval));
    assert(cNegInfInterval.contains(iInterval));
    assert(!cNegInfInterval.contains(posInfInterval));
    assert(!cNegInfInterval.contains(cPosInfInterval));
    assert(!cNegInfInterval.contains(iPosInfInterval));
    assert(cNegInfInterval.contains(negInfInterval));
    assert(cNegInfInterval.contains(cNegInfInterval));
    assert(cNegInfInterval.contains(iNegInfInterval));
    assert(iNegInfInterval.contains(interval));
    assert(iNegInfInterval.contains(cInterval));
    assert(iNegInfInterval.contains(iInterval));
    assert(!iNegInfInterval.contains(posInfInterval));
    assert(!iNegInfInterval.contains(cPosInfInterval));
    assert(!iNegInfInterval.contains(iPosInfInterval));
    assert(iNegInfInterval.contains(negInfInterval));
    assert(iNegInfInterval.contains(cNegInfInterval));
    assert(iNegInfInterval.contains(iNegInfInterval));

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(PosInfInterval!Date(Date(1999, 5, 4))));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(1996, 5, 4))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(2013, 7, 9))));
}

//Test NegInfInterval's isBefore(time point).
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    assert(!negInfInterval.isBefore(Date(2009, 7, 4)));
    assert(!negInfInterval.isBefore(Date(2010, 7, 3)));
    assert(!negInfInterval.isBefore(Date(2010, 7, 4)));
    assert(!negInfInterval.isBefore(Date(2010, 7, 5)));
    assert(!negInfInterval.isBefore(Date(2011, 7, 1)));
    assert(!negInfInterval.isBefore(Date(2012, 1, 6)));
    assert(negInfInterval.isBefore(Date(2012, 1, 7)));
    assert(negInfInterval.isBefore(Date(2012, 1, 8)));
    assert(negInfInterval.isBefore(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.isBefore(cdate));
    assert(!cNegInfInterval.isBefore(cdate));
    assert(!iNegInfInterval.isBefore(cdate));

    //Verify Examples.
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(1994, 12, 24)));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2000, 1, 5)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2012, 3, 1)));
}

//Test NegInfInterval's isBefore(Interval).
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
    {
        negInfInterval.isBefore(interval);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!negInfInterval.isBefore(negInfInterval));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!negInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(negInfInterval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(negInfInterval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 8))));

    assert(!NegInfInterval!Date(Date(2010, 7, 3)).isBefore(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 4)).isBefore(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 5)).isBefore(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 6)).isBefore(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 7)).isBefore(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 8)).isBefore(negInfInterval));

    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.isBefore(interval));
    assert(!negInfInterval.isBefore(cInterval));
    assert(!negInfInterval.isBefore(iInterval));
    assert(!negInfInterval.isBefore(posInfInterval));
    assert(!negInfInterval.isBefore(cPosInfInterval));
    assert(!negInfInterval.isBefore(iPosInfInterval));
    assert(!negInfInterval.isBefore(negInfInterval));
    assert(!negInfInterval.isBefore(cNegInfInterval));
    assert(!negInfInterval.isBefore(iNegInfInterval));
    assert(!cNegInfInterval.isBefore(interval));
    assert(!cNegInfInterval.isBefore(cInterval));
    assert(!cNegInfInterval.isBefore(iInterval));
    assert(!cNegInfInterval.isBefore(posInfInterval));
    assert(!cNegInfInterval.isBefore(cPosInfInterval));
    assert(!cNegInfInterval.isBefore(iPosInfInterval));
    assert(!cNegInfInterval.isBefore(negInfInterval));
    assert(!cNegInfInterval.isBefore(cNegInfInterval));
    assert(!cNegInfInterval.isBefore(iNegInfInterval));
    assert(!iNegInfInterval.isBefore(interval));
    assert(!iNegInfInterval.isBefore(cInterval));
    assert(!iNegInfInterval.isBefore(iInterval));
    assert(!iNegInfInterval.isBefore(posInfInterval));
    assert(!iNegInfInterval.isBefore(cPosInfInterval));
    assert(!iNegInfInterval.isBefore(iPosInfInterval));
    assert(!iNegInfInterval.isBefore(negInfInterval));
    assert(!iNegInfInterval.isBefore(cNegInfInterval));
    assert(!iNegInfInterval.isBefore(iNegInfInterval));

    //Verify Examples.
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(2012, 3, 1))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(1996, 5, 4))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(2013, 7, 9))));
}

//Test NegInfInterval's isAfter(time point).
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    assert(!negInfInterval.isAfter(Date(2009, 7, 4)));
    assert(!negInfInterval.isAfter(Date(2010, 7, 3)));
    assert(!negInfInterval.isAfter(Date(2010, 7, 4)));
    assert(!negInfInterval.isAfter(Date(2010, 7, 5)));
    assert(!negInfInterval.isAfter(Date(2011, 7, 1)));
    assert(!negInfInterval.isAfter(Date(2012, 1, 6)));
    assert(!negInfInterval.isAfter(Date(2012, 1, 7)));
    assert(!negInfInterval.isAfter(Date(2012, 1, 8)));
    assert(!negInfInterval.isAfter(Date(2013, 1, 7)));

    const cdate = Date(2010, 7, 6);
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.isAfter(cdate));
    assert(!cNegInfInterval.isAfter(cdate));
    assert(!iNegInfInterval.isAfter(cdate));
}

//Test NegInfInterval's isAfter(Interval).
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
    {
        negInfInterval.isAfter(interval);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!negInfInterval.isAfter(negInfInterval));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 8))));

    assert(!NegInfInterval!Date(Date(2010, 7, 3)).isAfter(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 4)).isAfter(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 5)).isAfter(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 6)).isAfter(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 7)).isAfter(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 8)).isAfter(negInfInterval));

    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.isAfter(interval));
    assert(!negInfInterval.isAfter(cInterval));
    assert(!negInfInterval.isAfter(iInterval));
    assert(!negInfInterval.isAfter(posInfInterval));
    assert(!negInfInterval.isAfter(cPosInfInterval));
    assert(!negInfInterval.isAfter(iPosInfInterval));
    assert(!negInfInterval.isAfter(negInfInterval));
    assert(!negInfInterval.isAfter(cNegInfInterval));
    assert(!negInfInterval.isAfter(iNegInfInterval));
    assert(!cNegInfInterval.isAfter(interval));
    assert(!cNegInfInterval.isAfter(cInterval));
    assert(!cNegInfInterval.isAfter(iInterval));
    assert(!cNegInfInterval.isAfter(posInfInterval));
    assert(!cNegInfInterval.isAfter(cPosInfInterval));
    assert(!cNegInfInterval.isAfter(iPosInfInterval));
    assert(!cNegInfInterval.isAfter(negInfInterval));
    assert(!cNegInfInterval.isAfter(cNegInfInterval));
    assert(!cNegInfInterval.isAfter(iNegInfInterval));
    assert(!iNegInfInterval.isAfter(interval));
    assert(!iNegInfInterval.isAfter(cInterval));
    assert(!iNegInfInterval.isAfter(iInterval));
    assert(!iNegInfInterval.isAfter(posInfInterval));
    assert(!iNegInfInterval.isAfter(cPosInfInterval));
    assert(!iNegInfInterval.isAfter(iPosInfInterval));
    assert(!iNegInfInterval.isAfter(negInfInterval));
    assert(!iNegInfInterval.isAfter(cNegInfInterval));
    assert(!iNegInfInterval.isAfter(iNegInfInterval));

    //Verify Examples.
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(1994, 12, 24)));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2000, 1, 5)));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2012, 3, 1)));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(2012, 3, 1))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(1996, 5, 4))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(2013, 7, 9))));
}

//Test NegInfInterval's intersects().
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
    {
        negInfInterval.intersects(interval);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(negInfInterval.intersects(negInfInterval));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(negInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(negInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(!negInfInterval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!negInfInterval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 8))));

    assert(NegInfInterval!Date(Date(2010, 7, 3)).intersects(negInfInterval));
    assert(NegInfInterval!Date(Date(2010, 7, 4)).intersects(negInfInterval));
    assert(NegInfInterval!Date(Date(2010, 7, 5)).intersects(negInfInterval));
    assert(NegInfInterval!Date(Date(2012, 1, 6)).intersects(negInfInterval));
    assert(NegInfInterval!Date(Date(2012, 1, 7)).intersects(negInfInterval));
    assert(NegInfInterval!Date(Date(2012, 1, 8)).intersects(negInfInterval));

    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(!negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(negInfInterval.intersects(interval));
    assert(negInfInterval.intersects(cInterval));
    assert(negInfInterval.intersects(iInterval));
    assert(negInfInterval.intersects(posInfInterval));
    assert(negInfInterval.intersects(cPosInfInterval));
    assert(negInfInterval.intersects(iPosInfInterval));
    assert(negInfInterval.intersects(negInfInterval));
    assert(negInfInterval.intersects(cNegInfInterval));
    assert(negInfInterval.intersects(iNegInfInterval));
    assert(cNegInfInterval.intersects(interval));
    assert(cNegInfInterval.intersects(cInterval));
    assert(cNegInfInterval.intersects(iInterval));
    assert(cNegInfInterval.intersects(posInfInterval));
    assert(cNegInfInterval.intersects(cPosInfInterval));
    assert(cNegInfInterval.intersects(iPosInfInterval));
    assert(cNegInfInterval.intersects(negInfInterval));
    assert(cNegInfInterval.intersects(cNegInfInterval));
    assert(cNegInfInterval.intersects(iNegInfInterval));
    assert(iNegInfInterval.intersects(interval));
    assert(iNegInfInterval.intersects(cInterval));
    assert(iNegInfInterval.intersects(iInterval));
    assert(iNegInfInterval.intersects(posInfInterval));
    assert(iNegInfInterval.intersects(cPosInfInterval));
    assert(iNegInfInterval.intersects(iPosInfInterval));
    assert(iNegInfInterval.intersects(negInfInterval));
    assert(iNegInfInterval.intersects(cNegInfInterval));
    assert(iNegInfInterval.intersects(iNegInfInterval));

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(2012, 3, 1))));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(1996, 5, 4))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(2013, 7, 9))));
}

//Test NegInfInterval's intersection().
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(I, J)(in I interval1, in J interval2)
    {
        interval1.intersection(interval2);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assertThrown!DateTimeException(testInterval(negInfInterval, PosInfInterval!Date(Date(2012, 1, 7))));
    assertThrown!DateTimeException(testInterval(negInfInterval, PosInfInterval!Date(Date(2012, 1, 8))));

    assert(negInfInterval.intersection(negInfInterval) ==
                negInfInterval);
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
                Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)));
    assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)));
    assert(negInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));
    assert(negInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)));

    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 3))) ==
                NegInfInterval!Date(Date(2010, 7, 3)));
    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2010, 7, 4)));
    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2010, 7, 5)));
    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 6)));
    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));

    assert(NegInfInterval!Date(Date(2010, 7, 3)).intersection(negInfInterval) ==
                NegInfInterval!Date(Date(2010, 7, 3)));
    assert(NegInfInterval!Date(Date(2010, 7, 4)).intersection(negInfInterval) ==
                NegInfInterval!Date(Date(2010, 7, 4)));
    assert(NegInfInterval!Date(Date(2010, 7, 5)).intersection(negInfInterval) ==
                NegInfInterval!Date(Date(2010, 7, 5)));
    assert(NegInfInterval!Date(Date(2012, 1, 6)).intersection(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 6)));
    assert(NegInfInterval!Date(Date(2012, 1, 7)).intersection(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 8)).intersection(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));

    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) ==
                Interval!Date(Date(2010, 7, 3), Date(2012, 1 ,7)));
    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) ==
                Interval!Date(Date(2010, 7, 4), Date(2012, 1 ,7)));
    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) ==
                Interval!Date(Date(2010, 7, 5), Date(2012, 1 ,7)));
    assert(negInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) ==
                Interval!Date(Date(2012, 1, 6), Date(2012, 1 ,7)));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.intersection(interval).empty);
    assert(!negInfInterval.intersection(cInterval).empty);
    assert(!negInfInterval.intersection(iInterval).empty);
    assert(!negInfInterval.intersection(posInfInterval).empty);
    assert(!negInfInterval.intersection(cPosInfInterval).empty);
    assert(!negInfInterval.intersection(iPosInfInterval).empty);
    assert(!negInfInterval.intersection(negInfInterval).empty);
    assert(!negInfInterval.intersection(cNegInfInterval).empty);
    assert(!negInfInterval.intersection(iNegInfInterval).empty);
    assert(!cNegInfInterval.intersection(interval).empty);
    assert(!cNegInfInterval.intersection(cInterval).empty);
    assert(!cNegInfInterval.intersection(iInterval).empty);
    assert(!cNegInfInterval.intersection(posInfInterval).empty);
    assert(!cNegInfInterval.intersection(cPosInfInterval).empty);
    assert(!cNegInfInterval.intersection(iPosInfInterval).empty);
    assert(!cNegInfInterval.intersection(negInfInterval).empty);
    assert(!cNegInfInterval.intersection(cNegInfInterval).empty);
    assert(!cNegInfInterval.intersection(iNegInfInterval).empty);
    assert(!iNegInfInterval.intersection(interval).empty);
    assert(!iNegInfInterval.intersection(cInterval).empty);
    assert(!iNegInfInterval.intersection(iInterval).empty);
    assert(!iNegInfInterval.intersection(posInfInterval).empty);
    assert(!iNegInfInterval.intersection(cPosInfInterval).empty);
    assert(!iNegInfInterval.intersection(iPosInfInterval).empty);
    assert(!iNegInfInterval.intersection(negInfInterval).empty);
    assert(!iNegInfInterval.intersection(cNegInfInterval).empty);
    assert(!iNegInfInterval.intersection(iNegInfInterval).empty);

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == Interval!Date(Date(1990, 7 , 6), Date(2000, 8, 2)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1)));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == NegInfInterval!Date(Date(1999, 7 , 6)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2012, 3 , 1)));
}

//Test NegInfInterval's isAdjacent().
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval)
    {
        negInfInterval.isAdjacent(interval);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(!negInfInterval.isAdjacent(negInfInterval));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))));
    assert(negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))));
    assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6))));
    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8))));

    assert(!NegInfInterval!Date(Date(2010, 7, 3)).isAdjacent(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 4)).isAdjacent(negInfInterval));
    assert(!NegInfInterval!Date(Date(2010, 7, 5)).isAdjacent(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 6)).isAdjacent(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 7)).isAdjacent(negInfInterval));
    assert(!NegInfInterval!Date(Date(2012, 1, 8)).isAdjacent(negInfInterval));

    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3))));
    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4))));
    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5))));
    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6))));
    assert(negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7))));
    assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.isAdjacent(interval));
    assert(!negInfInterval.isAdjacent(cInterval));
    assert(!negInfInterval.isAdjacent(iInterval));
    assert(!negInfInterval.isAdjacent(posInfInterval));
    assert(!negInfInterval.isAdjacent(cPosInfInterval));
    assert(!negInfInterval.isAdjacent(iPosInfInterval));
    assert(!negInfInterval.isAdjacent(negInfInterval));
    assert(!negInfInterval.isAdjacent(cNegInfInterval));
    assert(!negInfInterval.isAdjacent(iNegInfInterval));
    assert(!cNegInfInterval.isAdjacent(interval));
    assert(!cNegInfInterval.isAdjacent(cInterval));
    assert(!cNegInfInterval.isAdjacent(iInterval));
    assert(!cNegInfInterval.isAdjacent(posInfInterval));
    assert(!cNegInfInterval.isAdjacent(cPosInfInterval));
    assert(!cNegInfInterval.isAdjacent(iPosInfInterval));
    assert(!cNegInfInterval.isAdjacent(negInfInterval));
    assert(!cNegInfInterval.isAdjacent(cNegInfInterval));
    assert(!cNegInfInterval.isAdjacent(iNegInfInterval));
    assert(!iNegInfInterval.isAdjacent(interval));
    assert(!iNegInfInterval.isAdjacent(cInterval));
    assert(!iNegInfInterval.isAdjacent(iInterval));
    assert(!iNegInfInterval.isAdjacent(posInfInterval));
    assert(!iNegInfInterval.isAdjacent(cPosInfInterval));
    assert(!iNegInfInterval.isAdjacent(iPosInfInterval));
    assert(!iNegInfInterval.isAdjacent(negInfInterval));
    assert(!iNegInfInterval.isAdjacent(cNegInfInterval));
    assert(!iNegInfInterval.isAdjacent(iNegInfInterval));

    //Verify Examples.
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2012, 3, 1), Date(2019, 2, 2))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(1999, 5, 4))));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(2012, 3, 1))));

    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(1996, 5, 4))));
    assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(2012, 3, 1))));
}

//Test NegInfInterval's merge().
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(I, J)(in I interval1, in J interval2)
    {
        interval1.merge(interval2);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))));

    assert(negInfInterval.merge(negInfInterval) ==
                negInfInterval);
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                NegInfInterval!Date(Date(2013, 7, 3)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));
    assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 3))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    assert(NegInfInterval!Date(Date(2010, 7, 3)).merge(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2010, 7, 4)).merge(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2010, 7, 5)).merge(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 6)).merge(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 7)).merge(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 8)).merge(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 3)))));
    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 4)))));
    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 5)))));
    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 6)))));
    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 7)))));
    static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 8)))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.merge(interval).empty);
    assert(!negInfInterval.merge(cInterval).empty);
    assert(!negInfInterval.merge(iInterval).empty);
    static assert(!__traits(compiles, negInfInterval.merge(posInfInterval)));
    static assert(!__traits(compiles, negInfInterval.merge(cPosInfInterval)));
    static assert(!__traits(compiles, negInfInterval.merge(iPosInfInterval)));
    assert(!negInfInterval.merge(negInfInterval).empty);
    assert(!negInfInterval.merge(cNegInfInterval).empty);
    assert(!negInfInterval.merge(iNegInfInterval).empty);
    assert(!cNegInfInterval.merge(interval).empty);
    assert(!cNegInfInterval.merge(cInterval).empty);
    assert(!cNegInfInterval.merge(iInterval).empty);
    static assert(!__traits(compiles, cNegInfInterval.merge(posInfInterval)));
    static assert(!__traits(compiles, cNegInfInterval.merge(cPosInfInterval)));
    static assert(!__traits(compiles, cNegInfInterval.merge(iPosInfInterval)));
    assert(!cNegInfInterval.merge(negInfInterval).empty);
    assert(!cNegInfInterval.merge(cNegInfInterval).empty);
    assert(!cNegInfInterval.merge(iNegInfInterval).empty);
    assert(!iNegInfInterval.merge(interval).empty);
    assert(!iNegInfInterval.merge(cInterval).empty);
    assert(!iNegInfInterval.merge(iInterval).empty);
    static assert(!__traits(compiles, iNegInfInterval.merge(posInfInterval)));
    static assert(!__traits(compiles, iNegInfInterval.merge(cPosInfInterval)));
    static assert(!__traits(compiles, iNegInfInterval.merge(iPosInfInterval)));
    assert(!iNegInfInterval.merge(negInfInterval).empty);
    assert(!iNegInfInterval.merge(cNegInfInterval).empty);
    assert(!iNegInfInterval.merge(iNegInfInterval).empty);

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == NegInfInterval!Date(Date(2012, 3 , 1)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == NegInfInterval!Date(Date(2015, 9 , 2)));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(1999, 7, 6))) == NegInfInterval!Date(Date(2012, 3 , 1)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
}

//Test NegInfInterval's span().
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(I, J)(in I interval1, in J interval2)
    {
        interval1.span(interval2);
    }

    assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0))));

    assert(negInfInterval.span(negInfInterval) ==
                negInfInterval);
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) ==
                NegInfInterval!Date(Date(2013, 7, 3)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));
    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));
    assert(negInfInterval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) ==
                NegInfInterval!Date(Date(2012, 1, 9)));

    assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 3))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 4))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 5))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 6))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 7))) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 8))) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    assert(NegInfInterval!Date(Date(2010, 7, 3)).span(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2010, 7, 4)).span(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2010, 7, 5)).span(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 6)).span(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 7)).span(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 7)));
    assert(NegInfInterval!Date(Date(2012, 1, 8)).span(negInfInterval) ==
                NegInfInterval!Date(Date(2012, 1, 8)));

    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 3)))));
    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 4)))));
    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 5)))));
    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 6)))));
    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 7)))));
    static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 8)))));

    auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
    auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4));
    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!negInfInterval.span(interval).empty);
    assert(!negInfInterval.span(cInterval).empty);
    assert(!negInfInterval.span(iInterval).empty);
    static assert(!__traits(compiles, negInfInterval.span(posInfInterval)));
    static assert(!__traits(compiles, negInfInterval.span(cPosInfInterval)));
    static assert(!__traits(compiles, negInfInterval.span(iPosInfInterval)));
    assert(!negInfInterval.span(negInfInterval).empty);
    assert(!negInfInterval.span(cNegInfInterval).empty);
    assert(!negInfInterval.span(iNegInfInterval).empty);
    assert(!cNegInfInterval.span(interval).empty);
    assert(!cNegInfInterval.span(cInterval).empty);
    assert(!cNegInfInterval.span(iInterval).empty);
    static assert(!__traits(compiles, cNegInfInterval.span(posInfInterval)));
    static assert(!__traits(compiles, cNegInfInterval.span(cPosInfInterval)));
    static assert(!__traits(compiles, cNegInfInterval.span(iPosInfInterval)));
    assert(!cNegInfInterval.span(negInfInterval).empty);
    assert(!cNegInfInterval.span(cNegInfInterval).empty);
    assert(!cNegInfInterval.span(iNegInfInterval).empty);
    assert(!iNegInfInterval.span(interval).empty);
    assert(!iNegInfInterval.span(cInterval).empty);
    assert(!iNegInfInterval.span(iInterval).empty);
    static assert(!__traits(compiles, iNegInfInterval.span(posInfInterval)));
    static assert(!__traits(compiles, iNegInfInterval.span(cPosInfInterval)));
    static assert(!__traits(compiles, iNegInfInterval.span(iPosInfInterval)));
    assert(!iNegInfInterval.span(negInfInterval).empty);
    assert(!iNegInfInterval.span(cNegInfInterval).empty);
    assert(!iNegInfInterval.span(iNegInfInterval).empty);

    //Verify Examples.
    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == NegInfInterval!Date(Date(2012, 3 , 1)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == NegInfInterval!Date(Date(2015, 9 , 2)));
    assert(NegInfInterval!Date(Date(1600, 1, 7)).span(Interval!Date(Date(2012, 3, 11), Date(2017, 7, 1))) == NegInfInterval!Date(Date(2017, 7 , 1)));

    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(NegInfInterval!Date(Date(1999, 7, 6))) == NegInfInterval!Date(Date(2012, 3 , 1)));
    assert(NegInfInterval!Date(Date(2012, 3, 1)).span(NegInfInterval!Date(Date(2013, 1, 12))) == NegInfInterval!Date(Date(2013, 1 , 12)));
}

//Test NegInfInterval's shift().
unittest
{
    auto interval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
    {
        interval.shift(duration);
        assert(interval == expected);
    }

    testInterval(interval, dur!"days"(22), NegInfInterval!Date(Date(2012, 1, 29)));
    testInterval(interval, dur!"days"(-22), NegInfInterval!Date(Date(2011, 12, 16)));

    const cInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iInterval = NegInfInterval!Date(Date(2012, 1, 7));
    static assert(!__traits(compiles, cInterval.shift(dur!"days"(5))));
    static assert(!__traits(compiles, iInterval.shift(dur!"days"(5))));

    //Verify Examples.
    auto interval1 = NegInfInterval!Date(Date(2012, 4, 5));
    auto interval2 = NegInfInterval!Date(Date(2012, 4, 5));

    interval1.shift(dur!"days"(50));
    assert(interval1 == NegInfInterval!Date(Date(2012, 5, 25)));

    interval2.shift(dur!"days"(-50));
    assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15)));
}

//Test NegInfInterval's shift(int, int, AllowDayOverflow).
unittest
{
    {
        auto interval = NegInfInterval!Date(Date(2012, 1, 7));

        static void testIntervalFail(I)(I interval, int years, int months)
        {
            interval.shift(years, months);
        }

        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
        {
            interval.shift(years, months, allow);
            assert(interval == expected);
        }

        testInterval(interval, 5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2017, 1, 7)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2007, 1, 7)));

        auto interval2 = NegInfInterval!Date(Date(2010, 5, 31));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 7, 1)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 5, 1)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 5, 1)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 7, 1)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 6, 30)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 4, 30)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 4, 30)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 6, 30)));
    }

    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    static assert(!__traits(compiles, cNegInfInterval.shift(1)));
    static assert(!__traits(compiles, iNegInfInterval.shift(1)));

    //Verify Examples.
    auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
    auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));

    interval1.shift(2);
    assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));

    interval2.shift(-2);
    assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
}

//Test NegInfInterval's expand().
unittest
{
    auto interval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__)
    {
        interval.expand(duration);
        assert(interval == expected);
    }

    testInterval(interval, dur!"days"(22), NegInfInterval!Date(Date(2012, 1, 29)));
    testInterval(interval, dur!"days"(-22), NegInfInterval!Date(Date(2011, 12, 16)));

    const cInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iInterval = NegInfInterval!Date(Date(2012, 1, 7));
    static assert(!__traits(compiles, cInterval.expand(dur!"days"(5))));
    static assert(!__traits(compiles, iInterval.expand(dur!"days"(5))));

    //Verify Examples.
    auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
    auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));

    interval1.expand(dur!"days"(2));
    assert(interval1 == NegInfInterval!Date(Date(2012, 3, 3)));

    interval2.expand(dur!"days"(-2));
    assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28)));
}

//Test NegInfInterval's expand(int, int, AllowDayOverflow).
unittest
{
    {
        auto interval = NegInfInterval!Date(Date(2012, 1, 7));

        static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, in I expected, size_t line = __LINE__)
        {
            interval.expand(years, months, allow);
            assert(interval == expected);
        }

        testInterval(interval, 5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2017, 1, 7)));
        testInterval(interval, -5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2007, 1, 7)));

        auto interval2 = NegInfInterval!Date(Date(2010, 5, 31));

        testInterval(interval2, 1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 7, 1)));
        testInterval(interval2, 1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 5, 1)));
        testInterval(interval2, -1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 5, 1)));
        testInterval(interval2, -1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 7, 1)));

        testInterval(interval2, 1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 6, 30)));
        testInterval(interval2, 1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 4, 30)));
        testInterval(interval2, -1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 4, 30)));
        testInterval(interval2, -1, 1, AllowDayOverflow.no, NegInfInterval!Date( Date(2009, 6, 30)));
    }

    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    static assert(!__traits(compiles, cNegInfInterval.expand(1)));
    static assert(!__traits(compiles, iNegInfInterval.expand(1)));

    //Verify Examples.
    auto interval1 = NegInfInterval!Date(Date(2012, 3, 1));
    auto interval2 = NegInfInterval!Date(Date(2012, 3, 1));

    interval1.expand(2);
    assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1)));

    interval2.expand(-2);
    assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1)));
}

//Test NegInfInterval's bwdRange().
unittest
{
    auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

    static void testInterval(NegInfInterval!Date negInfInterval)
    {
        negInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.fwd)(DayOfWeek.fri)).popFront();
    }

    assertThrown!DateTimeException(testInterval(negInfInterval));

    assert(NegInfInterval!Date(Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).front ==
                Date(2010, 10, 1));

    assert(NegInfInterval!Date(Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).front ==
                Date(2010, 9, 24));

    //Verify Examples.
    auto interval = NegInfInterval!Date(Date(2010, 9, 9));
    auto func = delegate (in Date date)
                {
                    if((date.day & 1) == 0)
                        return date - dur!"days"(2);

                    return date - dur!"days"(1);
                };
    auto range = interval.bwdRange(func);

    //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8).
    assert(range.front == Date(2010, 9, 9));

    range.popFront();
    assert(range.front == Date(2010, 9, 8));

    range.popFront();
    assert(range.front == Date(2010, 9, 6));

    range.popFront();
    assert(range.front == Date(2010, 9, 4));

    range.popFront();
    assert(range.front == Date(2010, 9, 2));

    range.popFront();
    assert(!range.empty);

    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(!cNegInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty);
    assert(!iNegInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty);
}

//Test NegInfInterval's toString().
unittest
{
    assert(NegInfInterval!Date(Date(2012, 1, 7)).toString() == "[-∞ - 2012-Jan-07)");

    const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7));
    assert(cNegInfInterval.toString());
    assert(iNegInfInterval.toString());
}


/++
    Range-generating function.

    Returns a delegate which returns the next time point with the given
    $(D DayOfWeek) in a range.

    Using this delegate allows iteration over successive time points which
    are all the same day of the week. e.g. passing $(D DayOfWeek.mon) to
    $(D everyDayOfWeek) would result in a delegate which could be used to
    iterate over all of the Mondays in a range.

    Params:
        dir       = The direction to iterate in. If passing the return value to
                    $(D fwdRange), use $(D Direction.fwd). If passing it to
                    $(D bwdRange), use $(D Direction.bwd).
        dayOfWeek = The week that each time point in the range will be.
  +/
static TP delegate(in TP) everyDayOfWeek(TP, Direction dir = Direction.fwd)(DayOfWeek dayOfWeek) nothrow
    if(isTimePoint!TP &&
       (dir == Direction.fwd || dir == Direction.bwd) &&
       __traits(hasMember, TP, "dayOfWeek") &&
       !__traits(isStaticFunction, TP.dayOfWeek) &&
       is(typeof(TP.dayOfWeek) == DayOfWeek))
{
    TP func(in TP tp)
    {
        TP retval = cast(TP)tp;
        immutable days = daysToDayOfWeek(retval.dayOfWeek, dayOfWeek);

        static if(dir == Direction.fwd)
            immutable adjustedDays = days == 0 ? 7 : days;
        else
            immutable adjustedDays = days == 0 ? -7 : days - 7;

        return retval += dur!"days"(adjustedDays);
    }

    return &func;
}

///
unittest
{
    auto interval = Interval!Date(Date(2010, 9, 2), Date(2010, 9, 27));
    auto func = everyDayOfWeek!Date(DayOfWeek.mon);
    auto range = interval.fwdRange(func);

    //A Thursday. Using PopFirst.yes would have made this Date(2010, 9, 6).
    assert(range.front == Date(2010, 9, 2));

    range.popFront();
    assert(range.front == Date(2010, 9, 6));

    range.popFront();
    assert(range.front == Date(2010, 9, 13));

    range.popFront();
    assert(range.front == Date(2010, 9, 20));

    range.popFront();
    assert(range.empty);
}

unittest
{
    auto funcFwd = everyDayOfWeek!Date(DayOfWeek.mon);
    auto funcBwd = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.mon);

    assert(funcFwd(Date(2010, 8, 28)) == Date(2010, 8, 30));
    assert(funcFwd(Date(2010, 8, 29)) == Date(2010, 8, 30));
    assert(funcFwd(Date(2010, 8, 30)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 8, 31)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 9, 1)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 9, 2)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 9, 3)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 9, 4)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 9, 5)) == Date(2010, 9, 6));
    assert(funcFwd(Date(2010, 9, 6)) == Date(2010, 9, 13));
    assert(funcFwd(Date(2010, 9, 7)) == Date(2010, 9, 13));

    assert(funcBwd(Date(2010, 8, 28)) == Date(2010, 8, 23));
    assert(funcBwd(Date(2010, 8, 29)) == Date(2010, 8, 23));
    assert(funcBwd(Date(2010, 8, 30)) == Date(2010, 8, 23));
    assert(funcBwd(Date(2010, 8, 31)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 1)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 2)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 3)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 4)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 5)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 6)) == Date(2010, 8, 30));
    assert(funcBwd(Date(2010, 9, 7)) == Date(2010, 9, 6));

    static assert(!__traits(compiles, everyDayOfWeek!(TimeOfDay)(DayOfWeek.mon)));
    assert(everyDayOfWeek!(DateTime)(DayOfWeek.mon) !is null);
    assert(everyDayOfWeek!(SysTime)(DayOfWeek.mon) !is null);
}


/++
    Range-generating function.

    Returns a delegate which returns the next time point with the given month
    which would be reached by adding months to the given time point.

    So, using this delegate allows iteration over successive time points
    which are in the same month but different years. For example,
    iterate over each successive December 25th in an interval by starting with a
    date which had the 25th as its day and passed $(D Month.dec) to
    $(D everyMonth) to create the delegate.

    Since it wouldn't really make sense to be iterating over a specific month
    and end up with some of the time points in the succeeding month or two years
    after the previous time point, $(D AllowDayOverflow.no) is always used when
    calculating the next time point.

    Params:
        dir   = The direction to iterate in. If passing the return value to
                $(D fwdRange), use $(D Direction.fwd). If passing it to
                $(D bwdRange), use $(D Direction.bwd).
        month = The month that each time point in the range will be in.
  +/
static TP delegate(in TP) everyMonth(TP, Direction dir = Direction.fwd)(int month)
    if(isTimePoint!TP &&
       (dir == Direction.fwd || dir == Direction.bwd) &&
       __traits(hasMember, TP, "month") &&
       !__traits(isStaticFunction, TP.month) &&
       is(typeof(TP.month) == Month))
{
    enforceValid!"months"(month);

    TP func(in TP tp)
    {
        TP retval = cast(TP)tp;
        immutable months = monthsToMonth(retval.month, month);

        static if(dir == Direction.fwd)
            immutable adjustedMonths = months == 0 ? 12 : months;
        else
            immutable adjustedMonths = months == 0 ? -12 : months - 12;

        retval.add!"months"(adjustedMonths, AllowDayOverflow.no);

        if(retval.month != month)
        {
            retval.add!"months"(-1);
            assert(retval.month == month);
        }

        return retval;
    }

    return &func;
}

///
unittest
{
    auto interval = Interval!Date(Date(2000, 1, 30), Date(2004, 8, 5));
    auto func = everyMonth!(Date)(Month.feb);
    auto range = interval.fwdRange(func);

    //Using PopFirst.yes would have made this Date(2010, 2, 29).
    assert(range.front == Date(2000, 1, 30));

    range.popFront();
    assert(range.front == Date(2000, 2, 29));

    range.popFront();
    assert(range.front == Date(2001, 2, 28));

    range.popFront();
    assert(range.front == Date(2002, 2, 28));

    range.popFront();
    assert(range.front == Date(2003, 2, 28));

    range.popFront();
    assert(range.front == Date(2004, 2, 28));

    range.popFront();
    assert(range.empty);
}

unittest
{
    auto funcFwd = everyMonth!Date(Month.jun);
    auto funcBwd = everyMonth!(Date, Direction.bwd)(Month.jun);

    assert(funcFwd(Date(2010, 5, 31)) == Date(2010, 6, 30));
    assert(funcFwd(Date(2010, 6, 30)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2010, 7, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2010, 8, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2010, 9, 30)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2010, 10, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2010, 11, 30)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2010, 12, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2011, 1, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2011, 2, 28)) == Date(2011, 6, 28));
    assert(funcFwd(Date(2011, 3, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2011, 4, 30)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2011, 5, 31)) == Date(2011, 6, 30));
    assert(funcFwd(Date(2011, 6, 30)) == Date(2012, 6, 30));
    assert(funcFwd(Date(2011, 7, 31)) == Date(2012, 6, 30));

    assert(funcBwd(Date(2010, 5, 31)) == Date(2009, 6, 30));
    assert(funcBwd(Date(2010, 6, 30)) == Date(2009, 6, 30));
    assert(funcBwd(Date(2010, 7, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2010, 8, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2010, 9, 30)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2010, 10, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2010, 11, 30)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2010, 12, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2011, 1, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2011, 2, 28)) == Date(2010, 6, 28));
    assert(funcBwd(Date(2011, 3, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2011, 4, 30)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2011, 5, 31)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2011, 6, 30)) == Date(2010, 6, 30));
    assert(funcBwd(Date(2011, 7, 30)) == Date(2011, 6, 30));

    static assert(!__traits(compiles, everyMonth!(TimeOfDay)(Month.jan)));
    assert(everyMonth!(DateTime)(Month.jan) !is null);
    assert(everyMonth!(SysTime)(Month.jan) !is null);
}


/++
    Range-generating function.

    Returns a delegate which returns the next time point which is the given
    duration later.

    Using this delegate allows iteration over successive time points which
    are apart by the given duration e.g. passing $(D dur!"days"(3)) to
    $(D everyDuration) would result in a delegate which could be used to iterate
    over a range of days which are each 3 days apart.

    Params:
        dir      = The direction to iterate in. If passing the return value to
                   $(D fwdRange), use $(D Direction.fwd). If passing it to
                   $(D bwdRange), use $(D Direction.bwd).
        duration = The duration which separates each successive time point in
                   the range.
  +/
static TP delegate(in TP) everyDuration(TP, Direction dir = Direction.fwd, D)
                                       (D duration) nothrow
    if(isTimePoint!TP &&
       __traits(compiles, TP.init + duration) &&
       (dir == Direction.fwd || dir == Direction.bwd))
{
    TP func(in TP tp)
    {
        static if(dir == Direction.fwd)
            return tp + duration;
        else
            return tp - duration;
    }

    return &func;
}

///
unittest
{
    auto interval = Interval!Date(Date(2010, 9, 2), Date(2010, 9, 27));
    auto func = everyDuration!Date(dur!"days"(8));
    auto range = interval.fwdRange(func);

    //Using PopFirst.yes would have made this Date(2010, 9, 10).
    assert(range.front == Date(2010, 9, 2));

    range.popFront();
    assert(range.front == Date(2010, 9, 10));

    range.popFront();
    assert(range.front == Date(2010, 9, 18));

    range.popFront();
    assert(range.front == Date(2010, 9, 26));

    range.popFront();
    assert(range.empty);
}

unittest
{
    auto funcFwd = everyDuration!Date(dur!"days"(27));
    auto funcBwd = everyDuration!(Date, Direction.bwd)(dur!"days"(27));

    assert(funcFwd(Date(2009, 12, 25)) == Date(2010, 1, 21));
    assert(funcFwd(Date(2009, 12, 26)) == Date(2010, 1, 22));
    assert(funcFwd(Date(2009, 12, 27)) == Date(2010, 1, 23));
    assert(funcFwd(Date(2009, 12, 28)) == Date(2010, 1, 24));

    assert(funcBwd(Date(2010, 1, 21)) == Date(2009, 12, 25));
    assert(funcBwd(Date(2010, 1, 22)) == Date(2009, 12, 26));
    assert(funcBwd(Date(2010, 1, 23)) == Date(2009, 12, 27));
    assert(funcBwd(Date(2010, 1, 24)) == Date(2009, 12, 28));

    assert(everyDuration!Date(dur!"hnsecs"(1)) !is null);
    assert(everyDuration!TimeOfDay(dur!"hnsecs"(1)) !is null);
    assert(everyDuration!DateTime(dur!"hnsecs"(1)) !is null);
    assert(everyDuration!SysTime(dur!"hnsecs"(1)) !is null);
}


/++
    Range-generating function.

    Returns a delegate which returns the next time point which is the given
    number of years, month, and duration later.

    The difference between this version of $(D everyDuration) and the version
    which just takes a $(CXREF time, Duration) is that this one also takes the number of
    years and months (along with an $(D AllowDayOverflow) to indicate whether
    adding years and months should allow the days to overflow).

    Note that if iterating forward, $(D add!"years"()) is called on the given
    time point, then $(D add!"months"()), and finally the duration is added
    to it. However, if iterating backwards, the duration is added first, then
    $(D add!"months"()) is called, and finally $(D add!"years"()) is called.
    That way, going backwards generates close to the same time points that
    iterating forward does, but since adding years and months is not entirely
    reversible (due to possible day overflow, regardless of whether
    $(D AllowDayOverflow.yes) or $(D AllowDayOverflow.no) is used), it can't be
    guaranteed that iterating backwards will give the same time points as
    iterating forward would have (even assuming that the end of the range is a
    time point which would be returned by the delegate when iterating forward
    from $(D begin)).

    Params:
        dir           = The direction to iterate in. If passing the return
                        value to $(D fwdRange), use $(D Direction.fwd). If
                        passing it to $(D bwdRange), use $(D Direction.bwd).
        years         = The number of years to add to the time point passed to
                        the delegate.
        months        = The number of months to add to the time point passed to
                        the delegate.
        allowOverflow = Whether the days should be allowed to overflow on
                        $(D begin) and $(D end), causing their month to
                        increment.
        duration      = The duration to add to the time point passed to the
                        delegate.
  +/
static TP delegate(in TP) everyDuration(TP, Direction dir = Direction.fwd, D)
                                       (int years,
                                        int months = 0,
                                        AllowDayOverflow allowOverflow = AllowDayOverflow.yes,
                                        D duration = dur!"days"(0)) nothrow
    if(isTimePoint!TP &&
       __traits(compiles, TP.init + duration) &&
       __traits(compiles, TP.init.add!"years"(years)) &&
       __traits(compiles, TP.init.add!"months"(months)) &&
       (dir == Direction.fwd || dir == Direction.bwd))
{
    TP func(in TP tp)
    {
        static if(dir == Direction.fwd)
        {
            TP retval = cast(TP)tp;

            retval.add!"years"(years, allowOverflow);
            retval.add!"months"(months, allowOverflow);

            return retval + duration;
        }
        else
        {
            TP retval = tp - duration;

            retval.add!"months"(-months, allowOverflow);
            retval.add!"years"(-years, allowOverflow);

            return retval;
        }
    }

    return &func;
}

///
unittest
{
    auto interval = Interval!Date(Date(2010, 9, 2), Date(2025, 9, 27));
    auto func = everyDuration!Date(4, 1, AllowDayOverflow.yes, dur!"days"(2));
    auto range = interval.fwdRange(func);

    //Using PopFirst.yes would have made this Date(2014, 10, 12).
    assert(range.front == Date(2010, 9, 2));

    range.popFront();
    assert(range.front == Date(2014, 10, 4));

    range.popFront();
    assert(range.front == Date(2018, 11, 6));

    range.popFront();
    assert(range.front == Date(2022, 12, 8));

    range.popFront();
    assert(range.empty);
}

unittest
{
    {
        auto funcFwd = everyDuration!Date(1, 2, AllowDayOverflow.yes, dur!"days"(3));
        auto funcBwd = everyDuration!(Date, Direction.bwd)(1, 2, AllowDayOverflow.yes, dur!"days"(3));

        assert(funcFwd(Date(2009, 12, 25)) == Date(2011, 2, 28));
        assert(funcFwd(Date(2009, 12, 26)) == Date(2011, 3, 1));
        assert(funcFwd(Date(2009, 12, 27)) == Date(2011, 3, 2));
        assert(funcFwd(Date(2009, 12, 28)) == Date(2011, 3, 3));
        assert(funcFwd(Date(2009, 12, 29)) == Date(2011, 3, 4));

        assert(funcBwd(Date(2011, 2, 28)) == Date(2009, 12, 25));
        assert(funcBwd(Date(2011, 3, 1)) == Date(2009, 12, 26));
        assert(funcBwd(Date(2011, 3, 2)) == Date(2009, 12, 27));
        assert(funcBwd(Date(2011, 3, 3)) == Date(2009, 12, 28));
        assert(funcBwd(Date(2011, 3, 4)) == Date(2010, 1, 1));
    }

    {
        auto funcFwd = everyDuration!Date(1, 2, AllowDayOverflow.no, dur!"days"(3));
        auto funcBwd = everyDuration!(Date, Direction.bwd)(1, 2, AllowDayOverflow.yes, dur!"days"(3));

        assert(funcFwd(Date(2009, 12, 25)) == Date(2011, 2, 28));
        assert(funcFwd(Date(2009, 12, 26)) == Date(2011, 3, 1));
        assert(funcFwd(Date(2009, 12, 27)) == Date(2011, 3, 2));
        assert(funcFwd(Date(2009, 12, 28)) == Date(2011, 3, 3));
        assert(funcFwd(Date(2009, 12, 29)) == Date(2011, 3, 3));

        assert(funcBwd(Date(2011, 2, 28)) == Date(2009, 12, 25));
        assert(funcBwd(Date(2011, 3, 1)) == Date(2009, 12, 26));
        assert(funcBwd(Date(2011, 3, 2)) == Date(2009, 12, 27));
        assert(funcBwd(Date(2011, 3, 3)) == Date(2009, 12, 28));
        assert(funcBwd(Date(2011, 3, 4)) == Date(2010, 1, 1));
    }

    assert(everyDuration!Date(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null);
    static assert(!__traits(compiles, everyDuration!TimeOfDay(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1))));
    assert(everyDuration!DateTime(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null);
    assert(everyDuration!SysTime(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null);
}


//TODO Add function to create a range generating function based on a date recurrence pattern string.
//     This may or may not involve creating a date recurrence pattern class of some sort - probably
//     yes if we want to make it easy to build them. However, there is a standard recurrence
//     pattern string format which we'd want to support with a range generator (though if we have
//     the class/struct, we'd probably want a version of the range generating function which took
//     that rather than a string).


//==============================================================================
// Section with ranges.
//==============================================================================


/++
    A range over an $(LREF2 .Interval, Interval).

    $(D IntervalRange) is only ever constructed by $(LREF2 .Interval, Interval). However, when
    it is constructed, it is given a function, $(D func), which is used to
    generate the time points which are iterated over. $(D func) takes a time
    point and returns a time point of the same type. For instance,
    to iterate over all of the days in
    the interval $(D Interval!Date), pass a function to $(LREF2 .Interval, Interval)'s $(D fwdRange)
    where that function took a $(LREF Date) and returned a $(LREF Date) which was one
    day later. That function would then be used by $(D IntervalRange)'s
    $(D popFront) to iterate over the $(LREF Date)s in the interval.

    If $(D dir == Direction.fwd), then a range iterates forward in time, whereas
    if $(D dir == Direction.bwd), then it iterates backwards in time. So, if
    $(D dir == Direction.fwd) then $(D front == interval.begin), whereas if
    $(D dir == Direction.bwd) then $(D front == interval.end). $(D func) must
    generate a time point going in the proper direction of iteration, or a
    $(LREF DateTimeException) will be thrown. So, to iterate forward in
    time, the time point that $(D func) generates must be later in time than the
    one passed to it. If it's either identical or earlier in time, then a
    $(LREF DateTimeException) will be thrown. To iterate backwards, then
    the generated time point must be before the time point which was passed in.

    If the generated time point is ever passed the edge of the range in the
    proper direction, then the edge of that range will be used instead. So, if
    iterating forward, and the generated time point is past the interval's
    $(D end), then $(D front) becomes $(D end). If iterating backwards, and the
    generated time point is before $(D begin), then $(D front) becomes
    $(D begin). In either case, the range would then be empty.

    Also note that while normally the $(D begin) of an interval is included in
    it and its $(D end) is excluded from it, if $(D dir == Direction.bwd), then
    $(D begin) is treated as excluded and $(D end) is treated as included. This
    allows for the same behavior in both directions. This works because none of
    $(LREF2 .Interval, Interval)'s functions which care about whether $(D begin) or $(D end) is
    included or excluded are ever called by $(D IntervalRange). $(D interval)
    returns a normal interval, regardless of whether $(D dir == Direction.fwd)
    or if $(D dir == Direction.bwd), so any $(LREF2 .Interval, Interval) functions which are
    called on it which care about whether $(D begin) or $(D end) are included or
    excluded will treat $(D begin) as included and $(D end) as excluded.
  +/
struct IntervalRange(TP, Direction dir)
    if(isTimePoint!TP && dir != Direction.both)
{
public:

    /++
        Params:
            rhs = The $(D IntervalRange) to assign to this one.
      +/
    ref IntervalRange opAssign(ref IntervalRange rhs) pure nothrow
    {
        _interval = rhs._interval;
        _func = rhs._func;
        return this;
    }


    /++ Ditto +/
    ref IntervalRange opAssign(IntervalRange rhs) pure nothrow
    {
        return this = rhs;
    }


    /++
        Whether this $(D IntervalRange) is empty.
      +/
    @property bool empty() const pure nothrow
    {
        return _interval.empty;
    }


    /++
        The first time point in the range.

        Throws:
            $(LREF DateTimeException) if the range is empty.
      +/
    @property TP front() const pure
    {
        _enforceNotEmpty();

        static if(dir == Direction.fwd)
            return _interval.begin;
        else
            return _interval.end;
    }


    /++
        Pops $(D front) from the range, using $(D func) to generate the next
        time point in the range. If the generated time point is beyond the edge
        of the range, then $(D front) is set to that edge, and the range is then
        empty. So, if iterating forwards, and the generated time point is
        greater than the interval's $(D end), then $(D front) is set to
        $(D end). If iterating backwards, and the generated time point is less
        than the interval's $(D begin), then $(D front) is set to $(D begin).

        Throws:
            $(LREF DateTimeException) if the range is empty or if the generated
            time point is in the wrong direction (i.e. if iterating
            forward and the generated time point is before $(D front), or if
            iterating backwards and the generated time point is after
            $(D front)).
      +/
    void popFront()
    {
        _enforceNotEmpty();

        static if(dir == Direction.fwd)
        {
            auto begin = _func(_interval.begin);

            if(begin > _interval.end)
                begin = _interval.end;

            _enforceCorrectDirection(begin);

            _interval.begin = begin;
        }
        else
        {
            auto end = _func(_interval.end);

            if(end < _interval.begin)
                end = _interval.begin;

            _enforceCorrectDirection(end);

            _interval.end = end;
        }
    }


    /++
        Returns a copy of $(D this).
      +/
    @property IntervalRange save() pure nothrow
    {
        return this;
    }


    /++
        The interval that this $(D IntervalRange) currently covers.
      +/
    @property Interval!TP interval() const pure nothrow
    {
        return cast(Interval!TP)_interval;
    }


    /++
        The function used to generate the next time point in the range.
      +/
    TP delegate(in TP) func() pure nothrow @property
    {
        return _func;
    }


    /++
        The $(D Direction) that this range iterates in.
      +/
    @property Direction direction() const pure nothrow
    {
        return dir;
    }


private:

    /+
        Params:
            interval = The interval that this range covers.
            func     = The function used to generate the time points which are
                       iterated over.
      +/
    this(in Interval!TP interval, TP delegate(in TP) func) pure nothrow
    {
        _func = func;
        _interval = interval;
    }


    /+
        Throws:
            $(LREF DateTimeException) if this interval is empty.
      +/
    void _enforceNotEmpty(size_t line = __LINE__) const pure
    {
        if(empty)
            throw new DateTimeException("Invalid operation for an empty IntervalRange.", __FILE__, line);
    }


    /+
        Throws:
            $(LREF DateTimeException) if $(D_PARAM newTP) is in the wrong
            direction.
      +/
    void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const
    {
        import std.format : format;

        static if(dir == Direction.fwd)
        {
            enforce(newTP > _interval._begin,
                    new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]",
                                                 interval._begin,
                                                 newTP),
                                                 __FILE__,
                                                 line));
        }
        else
        {
            enforce(newTP < _interval._end,
                    new DateTimeException(format("Generated time point is after previous end: prev [%s] new [%s]",
                                                 interval._end,
                                                 newTP),
                                                 __FILE__,
                                                 line));
        }
    }


    Interval!TP        _interval;
    TP delegate(in TP) _func;
}

//Test that IntervalRange satisfies the range predicates that it's supposed to satisfy.
unittest
{
    static assert(isInputRange!(IntervalRange!(Date, Direction.fwd)));
    static assert(isForwardRange!(IntervalRange!(Date, Direction.fwd)));

    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895
    //static assert(!isOutputRange!(IntervalRange!(Date, Direction.fwd), Date));

    static assert(!isBidirectionalRange!(IntervalRange!(Date, Direction.fwd)));
    static assert(!isRandomAccessRange!(IntervalRange!(Date, Direction.fwd)));
    static assert(!hasSwappableElements!(IntervalRange!(Date, Direction.fwd)));
    static assert(!hasAssignableElements!(IntervalRange!(Date, Direction.fwd)));
    static assert(!hasLength!(IntervalRange!(Date, Direction.fwd)));
    static assert(!isInfinite!(IntervalRange!(Date, Direction.fwd)));
    static assert(!hasSlicing!(IntervalRange!(Date, Direction.fwd)));

    static assert(is(ElementType!(IntervalRange!(Date, Direction.fwd)) == Date));
    static assert(is(ElementType!(IntervalRange!(TimeOfDay, Direction.fwd)) == TimeOfDay));
    static assert(is(ElementType!(IntervalRange!(DateTime, Direction.fwd)) == DateTime));
    static assert(is(ElementType!(IntervalRange!(SysTime, Direction.fwd)) == SysTime));
}

//Test construction of IntervalRange.
unittest
{
    {
        Date dateFunc(in Date date)
        {
            return date;
        }

        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));

        auto ir = IntervalRange!(Date, Direction.fwd)(interval, &dateFunc);
    }

    {
        TimeOfDay todFunc(in TimeOfDay tod)
        {
            return tod;
        }

        auto interval = Interval!TimeOfDay(TimeOfDay(12, 1, 7), TimeOfDay(14, 0, 0));

        auto ir = IntervalRange!(TimeOfDay, Direction.fwd)(interval, &todFunc);
    }

    {
        DateTime dtFunc(in DateTime dt)
        {
            return dt;
        }

        auto interval = Interval!DateTime(DateTime(2010, 7, 4, 12, 1, 7), DateTime(2012, 1, 7, 14, 0, 0));

        auto ir = IntervalRange!(DateTime, Direction.fwd)(interval, &dtFunc);
    }

    {
        SysTime stFunc(in SysTime st)
        {
            return cast(SysTime)st;
        }

        auto interval = Interval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7)), SysTime(DateTime(2012, 1, 7, 14, 0, 0)));

        auto ir = IntervalRange!(SysTime, Direction.fwd)(interval, &stFunc);
    }
}

//Test IntervalRange's empty().
unittest
{
    //fwd
    {
        auto range = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));

        assert(!range.empty);
        range.popFront();
        assert(range.empty);

        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
        assert(!cRange.empty);

        //Apparently, creating an immutable IntervalRange!Date doesn't work, so we can't test if
        //empty works with it. However, since an immutable range is pretty useless, it's no great loss.
    }

    //bwd
    {
        auto range = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));

        assert(!range.empty);
        range.popFront();
        assert(range.empty);

        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
        assert(!cRange.empty);

        //Apparently, creating an immutable IntervalRange!Date doesn't work, so we can't test if
        //empty works with it. However, since an immutable range is pretty useless, it's no great loss.
    }
}

//Test IntervalRange's front.
unittest
{
    //fwd
    {
        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
        assertThrown!DateTimeException((in IntervalRange!(Date, Direction.fwd) range){range.front;}(emptyRange));

        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed));
        assert(range.front == Date(2010, 7, 4));

        auto poppedRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
        assert(poppedRange.front == Date(2010, 7, 7));

        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
        assert(cRange.front != Date.init);
    }

    //bwd
    {
        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
        assertThrown!DateTimeException((in IntervalRange!(Date, Direction.bwd) range){range.front;}(emptyRange));

        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed));
        assert(range.front == Date(2012, 1, 7));

        auto poppedRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
        assert(poppedRange.front == Date(2012, 1, 4));

        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
        assert(cRange.front != Date.init);
    }
}

//Test IntervalRange's popFront().
unittest
{
    //fwd
    {
        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
        assertThrown!DateTimeException((IntervalRange!(Date, Direction.fwd) range){range.popFront();}(emptyRange));

        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
        auto expected = range.front;

        foreach(date; range)
        {
            assert(date == expected);
            expected += dur!"days"(7);
        }

        assert(walkLength(range) == 79);

        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
        assert(cRange.front != Date.init);
    }

    //bwd
    {
        auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
        assertThrown!DateTimeException((IntervalRange!(Date, Direction.bwd) range){range.popFront();}(emptyRange));

        auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
        auto expected = range.front;

        foreach(date; range)
        {
            assert(date == expected);
            expected += dur!"days"(-7);
        }

        assert(walkLength(range) == 79);

        const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
        static assert(!__traits(compiles, cRange.popFront()));
    }
}

//Test IntervalRange's save.
unittest
{
    //fwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
        auto range = interval.fwdRange(func);

        assert(range.save == range);
    }

    //bwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
        auto range = interval.bwdRange(func);

        assert(range.save == range);
    }
}

//Test IntervalRange's interval.
unittest
{
    //fwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
        auto range = interval.fwdRange(func);

        assert(range.interval == interval);

        const cRange = range;
        assert(!cRange.interval.empty);
    }

    //bwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
        auto range = interval.bwdRange(func);

        assert(range.interval == interval);

        const cRange = range;
        assert(!cRange.interval.empty);
    }
}

//Test IntervalRange's func.
unittest
{
    //fwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
        auto range = interval.fwdRange(func);

        assert(range.func == func);
    }

    //bwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
        auto range = interval.bwdRange(func);

        assert(range.func == func);
    }
}

//Test IntervalRange's direction.
unittest
{
    //fwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!Date(DayOfWeek.fri);
        auto range = interval.fwdRange(func);

        assert(range.direction == Direction.fwd);

        const cRange = range;
        assert(cRange.direction == Direction.fwd);
    }

    //bwd
    {
        auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7));
        auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
        auto range = interval.bwdRange(func);

        assert(range.direction == Direction.bwd);

        const cRange = range;
        assert(cRange.direction == Direction.bwd);
    }
}


/++
    A range over a $(D PosInfInterval). It is an infinite range.

    $(D PosInfIntervalRange) is only ever constructed by $(D PosInfInterval).
    However, when it is constructed, it is given a function, $(D func), which
    is used to generate the time points which are iterated over. $(D func)
    takes a time point and returns a time point of the same type. For
    instance, to iterate
    over all of the days in the interval $(D PosInfInterval!Date), pass a function to
    $(D PosInfInterval)'s $(D fwdRange) where that function took a $(LREF Date) and
    returned a $(LREF Date) which was one day later. That function would then be
    used by $(D PosInfIntervalRange)'s $(D popFront) to iterate over the
    $(LREF Date)s in the interval - though obviously, since the range is infinite,
    use a function such as $(D std.range.take) with it rather than
    iterating over $(I all) of the dates.

    As the interval goes to positive infinity, the range is always iterated over
    forwards, never backwards. $(D func) must generate a time point going in
    the proper direction of iteration, or a $(LREF DateTimeException) will be
    thrown. So, the time points that $(D func) generates must be later in time
    than the one passed to it. If it's either identical or earlier in time, then
    a $(LREF DateTimeException) will be thrown.
  +/
struct PosInfIntervalRange(TP)
    if(isTimePoint!TP)
{
public:

    /++
        Params:
            rhs = The $(D PosInfIntervalRange) to assign to this one.
      +/
    ref PosInfIntervalRange opAssign(ref PosInfIntervalRange rhs) pure nothrow
    {
        _interval = rhs._interval;
        _func = rhs._func;

        return this;
    }


    /++ Ditto +/
    ref PosInfIntervalRange opAssign(PosInfIntervalRange rhs) pure nothrow
    {
        return this = rhs;
    }


    /++
        This is an infinite range, so it is never empty.
      +/
    enum bool empty = false;


    /++
        The first time point in the range.
      +/
    @property TP front() const pure nothrow
    {
        return _interval.begin;
    }


    /++
        Pops $(D front) from the range, using $(D func) to generate the next
        time point in the range.

        Throws:
            $(LREF DateTimeException) if the generated time point is less than
            $(D front).
      +/
    void popFront()
    {
        auto begin = _func(_interval.begin);

        _enforceCorrectDirection(begin);

        _interval.begin = begin;
    }


    /++
        Returns a copy of $(D this).
      +/
    @property PosInfIntervalRange save() pure nothrow
    {
        return this;
    }


    /++
        The interval that this range currently covers.
      +/
    @property PosInfInterval!TP interval() const pure nothrow
    {
        return cast(PosInfInterval!TP)_interval;
    }


    /++
        The function used to generate the next time point in the range.
      +/
    TP delegate(in TP) func() pure nothrow @property
    {
        return _func;
    }


private:

    /+
        Params:
            interval = The interval that this range covers.
            func     = The function used to generate the time points which are
                       iterated over.
      +/
    this(in PosInfInterval!TP interval, TP delegate(in TP) func) pure nothrow
    {
        _func = func;
        _interval = interval;
    }


    /+
        Throws:
            $(LREF DateTimeException) if $(D_PARAME newTP) is in the wrong
            direction.
      +/
    void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const
    {
        import std.format : format;

        enforce(newTP > _interval._begin,
                new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]",
                                             interval._begin,
                                             newTP),
                                             __FILE__,
                                             line));
    }


    PosInfInterval!TP  _interval;
    TP delegate(in TP) _func;
}

//Test that PosInfIntervalRange satisfies the range predicates that it's supposed to satisfy.
unittest
{
    static assert(isInputRange!(PosInfIntervalRange!Date));
    static assert(isForwardRange!(PosInfIntervalRange!Date));
    static assert(isInfinite!(PosInfIntervalRange!Date));

    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895
    //static assert(!isOutputRange!(PosInfIntervalRange!Date, Date));
    static assert(!isBidirectionalRange!(PosInfIntervalRange!Date));
    static assert(!isRandomAccessRange!(PosInfIntervalRange!Date));
    static assert(!hasSwappableElements!(PosInfIntervalRange!Date));
    static assert(!hasAssignableElements!(PosInfIntervalRange!Date));
    static assert(!hasLength!(PosInfIntervalRange!Date));
    static assert(!hasSlicing!(PosInfIntervalRange!Date));

    static assert(is(ElementType!(PosInfIntervalRange!Date) == Date));
    static assert(is(ElementType!(PosInfIntervalRange!TimeOfDay) == TimeOfDay));
    static assert(is(ElementType!(PosInfIntervalRange!DateTime) == DateTime));
    static assert(is(ElementType!(PosInfIntervalRange!SysTime) == SysTime));
}

//Test construction of PosInfIntervalRange.
unittest
{
    {
        Date dateFunc(in Date date)
        {
            return date;
        }

        auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4));

        auto ir = PosInfIntervalRange!Date(posInfInterval, &dateFunc);
    }

    {
        TimeOfDay todFunc(in TimeOfDay tod)
        {
            return tod;
        }

        auto posInfInterval = PosInfInterval!TimeOfDay(TimeOfDay(12, 1, 7));

        auto ir = PosInfIntervalRange!(TimeOfDay)(posInfInterval, &todFunc);
    }

    {
        DateTime dtFunc(in DateTime dt)
        {
            return dt;
        }

        auto posInfInterval = PosInfInterval!DateTime(DateTime(2010, 7, 4, 12, 1, 7));

        auto ir = PosInfIntervalRange!(DateTime)(posInfInterval, &dtFunc);
    }

    {
        SysTime stFunc(in SysTime st)
        {
            return cast(SysTime)st;
        }

        auto posInfInterval = PosInfInterval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7)));

        auto ir = PosInfIntervalRange!(SysTime)(posInfInterval, &stFunc);
    }
}

//Test PosInfIntervalRange's front.
unittest
{
    auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed));
    assert(range.front == Date(2010, 7, 4));

    auto poppedRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
    assert(poppedRange.front == Date(2010, 7, 7));

    const cRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
    assert(cRange.front != Date.init);
}

//Test PosInfIntervalRange's popFront().
unittest
{
    import std.range;
    auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes);
    auto expected = range.front;

    foreach(date; take(range, 79))
    {
        assert(date == expected);
        expected += dur!"days"(7);
    }

    const cRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri));
    static assert(!__traits(compiles, cRange.popFront()));
}

//Test PosInfIntervalRange's save.
unittest
{
    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
    auto func = everyDayOfWeek!Date(DayOfWeek.fri);
    auto range = interval.fwdRange(func);

    assert(range.save == range);
}

//Test PosInfIntervalRange's interval.
unittest
{
    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
    auto func = everyDayOfWeek!Date(DayOfWeek.fri);
    auto range = interval.fwdRange(func);

    assert(range.interval == interval);

    const cRange = range;
    assert(!cRange.interval.empty);
}

//Test PosInfIntervalRange's func.
unittest
{
    auto interval = PosInfInterval!Date(Date(2010, 7, 4));
    auto func = everyDayOfWeek!Date(DayOfWeek.fri);
    auto range = interval.fwdRange(func);

    assert(range.func == func);
}


/++
    A range over a $(D NegInfInterval). It is an infinite range.

    $(D NegInfIntervalRange) is only ever constructed by $(D NegInfInterval).
    However, when it is constructed, it is given a function, $(D func), which
    is used to generate the time points which are iterated over. $(D func)
    takes a time point and returns a time point of the same type. For
    instance, to iterate
    over all of the days in the interval $(D NegInfInterval!Date), pass a function to
    $(D NegInfInterval)'s $(D bwdRange) where that function took a $(LREF Date) and
    returned a $(LREF Date) which was one day earlier. That function would then be
    used by $(D NegInfIntervalRange)'s $(D popFront) to iterate over the
    $(LREF Date)s in the interval - though obviously, since the range is infinite,
    use a function such as $(D std.range.take) with it rather than
    iterating over $(I all) of the dates.

    As the interval goes to negative infinity, the range is always iterated over
    backwards, never forwards. $(D func) must generate a time point going in
    the proper direction of iteration, or a $(LREF DateTimeException) will be
    thrown. So, the time points that $(D func) generates must be earlier in time
    than the one passed to it. If it's either identical or later in time, then a
    $(LREF DateTimeException) will be thrown.

    Also note that while normally the $(D end) of an interval is excluded from
    it, $(D NegInfIntervalRange) treats it as if it were included. This allows
    for the same behavior as with $(D PosInfIntervalRange). This works
    because none of $(D NegInfInterval)'s functions which care about whether
    $(D end) is included or excluded are ever called by
    $(D NegInfIntervalRange). $(D interval) returns a normal interval, so any
    $(D NegInfInterval) functions which are called on it which care about
    whether $(D end) is included or excluded will treat $(D end) as excluded.
  +/
struct NegInfIntervalRange(TP)
    if(isTimePoint!TP)
{
public:

    /++
        Params:
            rhs = The $(D NegInfIntervalRange) to assign to this one.
      +/
    ref NegInfIntervalRange opAssign(ref NegInfIntervalRange rhs) pure nothrow
    {
        _interval = rhs._interval;
        _func = rhs._func;

        return this;
    }


    /++ Ditto +/
    ref NegInfIntervalRange opAssign(NegInfIntervalRange rhs) pure nothrow
    {
        return this = rhs;
    }


    /++
        This is an infinite range, so it is never empty.
      +/
    enum bool empty = false;


    /++
        The first time point in the range.
      +/
    @property TP front() const pure nothrow
    {
        return _interval.end;
    }


    /++
        Pops $(D front) from the range, using $(D func) to generate the next
        time point in the range.

        Throws:
            $(LREF DateTimeException) if the generated time point is greater than
            $(D front).
      +/
    void popFront()
    {
        auto end = _func(_interval.end);

        _enforceCorrectDirection(end);

        _interval.end = end;
    }


    /++
        Returns a copy of $(D this).
      +/
    @property NegInfIntervalRange save() pure nothrow
    {
        return this;
    }


    /++
        The interval that this range currently covers.
      +/
    @property NegInfInterval!TP interval() const pure nothrow
    {
        return cast(NegInfInterval!TP)_interval;
    }


    /++
        The function used to generate the next time point in the range.
      +/
    TP delegate(in TP) func() pure nothrow @property
    {
        return _func;
    }


private:

    /+
        Params:
            interval = The interval that this range covers.
            func     = The function used to generate the time points which are
                       iterated over.
      +/
    this(in NegInfInterval!TP interval, TP delegate(in TP) func) pure nothrow
    {
        _func = func;
        _interval = interval;
    }


    /+
        Throws:
            $(LREF DateTimeException) if $(D_PARAM newTP) is in the wrong
            direction.
      +/
    void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const
    {
        import std.format : format;

        enforce(newTP < _interval._end,
                new DateTimeException(format("Generated time point is before previous end: prev [%s] new [%s]",
                                             interval._end,
                                             newTP),
                                             __FILE__,
                                             line));
    }


    NegInfInterval!TP  _interval;
    TP delegate(in TP) _func;
}

//Test that NegInfIntervalRange satisfies the range predicates that it's supposed to satisfy.
unittest
{
    static assert(isInputRange!(NegInfIntervalRange!Date));
    static assert(isForwardRange!(NegInfIntervalRange!Date));
    static assert(isInfinite!(NegInfIntervalRange!Date));

    //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895
    //static assert(!isOutputRange!(NegInfIntervalRange!Date, Date));
    static assert(!isBidirectionalRange!(NegInfIntervalRange!Date));
    static assert(!isRandomAccessRange!(NegInfIntervalRange!Date));
    static assert(!hasSwappableElements!(NegInfIntervalRange!Date));
    static assert(!hasAssignableElements!(NegInfIntervalRange!Date));
    static assert(!hasLength!(NegInfIntervalRange!Date));
    static assert(!hasSlicing!(NegInfIntervalRange!Date));

    static assert(is(ElementType!(NegInfIntervalRange!Date) == Date));
    static assert(is(ElementType!(NegInfIntervalRange!TimeOfDay) == TimeOfDay));
    static assert(is(ElementType!(NegInfIntervalRange!DateTime) == DateTime));
}

//Test construction of NegInfIntervalRange.
unittest
{
    {
        Date dateFunc(in Date date)
        {
            return date;
        }

        auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7));

        auto ir = NegInfIntervalRange!Date(negInfInterval, &dateFunc);
    }

    {
        TimeOfDay todFunc(in TimeOfDay tod)
        {
            return tod;
        }

        auto negInfInterval = NegInfInterval!TimeOfDay(TimeOfDay(14, 0, 0));

        auto ir = NegInfIntervalRange!(TimeOfDay)(negInfInterval, &todFunc);
    }

    {
        DateTime dtFunc(in DateTime dt)
        {
            return dt;
        }

        auto negInfInterval = NegInfInterval!DateTime(DateTime(2012, 1, 7, 14, 0, 0));

        auto ir = NegInfIntervalRange!(DateTime)(negInfInterval, &dtFunc);
    }

    {
        SysTime stFunc(in SysTime st)
        {
            return cast(SysTime)(st);
        }

        auto negInfInterval = NegInfInterval!SysTime(SysTime(DateTime(2012, 1, 7, 14, 0, 0)));

        auto ir = NegInfIntervalRange!(SysTime)(negInfInterval, &stFunc);
    }
}

//Test NegInfIntervalRange's front.
unittest
{
    auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed));
    assert(range.front == Date(2012, 1, 7));

    auto poppedRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
    assert(poppedRange.front == Date(2012, 1, 4));

    const cRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
    assert(cRange.front != Date.init);
}

//Test NegInfIntervalRange's popFront().
unittest
{
    import std.range;

    auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes);
    auto expected = range.front;

    foreach(date; take(range, 79))
    {
        assert(date == expected);
        expected += dur!"days"(-7);
    }

    const cRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri));
    static assert(!__traits(compiles, cRange.popFront()));
}

//Test NegInfIntervalRange's save.
unittest
{
    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
    auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
    auto range = interval.bwdRange(func);

    assert(range.save == range);
}

//Test NegInfIntervalRange's interval.
unittest
{
    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
    auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
    auto range = interval.bwdRange(func);

    assert(range.interval == interval);

    const cRange = range;
    assert(!cRange.interval.empty);
}

//Test NegInfIntervalRange's func.
unittest
{
    auto interval = NegInfInterval!Date(Date(2012, 1, 7));
    auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri);
    auto range = interval.bwdRange(func);

    assert(range.func == func);
}


//==============================================================================
// Section with time zones.
//==============================================================================

/++
    Represents a time zone. It is used with $(LREF SysTime) to indicate the time
    zone of a $(LREF SysTime).
  +/
abstract class TimeZone
{
public:

    /++
        The name of the time zone per the TZ Database. This is the name used to
        get a $(LREF2 .TimeZone, TimeZone) by name with $(D TimeZone.getTimeZone).

        See_Also:
            $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
              Database)<br>
            $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
              Time Zones)
      +/
    @property string name() @safe const nothrow
    {
        return _name;
    }


    /++
        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
        when DST is $(I not) in effect (e.g. PST). It is not necessarily unique.

        However, on Windows, it may be the unabbreviated name (e.g. Pacific
        Standard Time). Regardless, it is not the same as name.
      +/
    @property string stdName() @safe const nothrow
    {
        return _stdName;
    }


    /++
        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
        when DST $(I is) in effect (e.g. PDT). It is not necessarily unique.

        However, on Windows, it may be the unabbreviated name (e.g. Pacific
        Daylight Time). Regardless, it is not the same as name.
      +/
    @property string dstName() @safe const nothrow
    {
        return _dstName;
    }


    /++
        Whether this time zone has Daylight Savings Time at any point in time.
        Note that for some time zone types it may not have DST for current dates
        but will still return true for $(D hasDST) because the time zone did at
        some point have DST.
      +/
    @property abstract bool hasDST() @safe const nothrow;


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in UTC time (i.e. std time) and returns whether DST is effect in this
        time zone at the given point in time.

        Params:
            stdTime = The UTC time that needs to be checked for DST in this time
                      zone.
      +/
    abstract bool dstInEffect(long stdTime) @safe const nothrow;


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in UTC time (i.e. std time) and converts it to this time zone's time.

        Params:
            stdTime = The UTC time that needs to be adjusted to this time zone's
                      time.
      +/
    abstract long utcToTZ(long stdTime) @safe const nothrow;


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in this time zone's time and converts it to UTC (i.e. std time).

        Params:
            adjTime = The time in this time zone that needs to be adjusted to
                      UTC time.
      +/
    abstract long tzToUTC(long adjTime) @safe const nothrow;


    /++
        Returns what the offset from UTC is at the given std time.
        It includes the DST offset in effect at that time (if any).

        Params:
            stdTime = The UTC time for which to get the offset from UTC for this
                      time zone.
      +/
    Duration utcOffsetAt(long stdTime) @safe const nothrow
    {
        return dur!"hnsecs"(utcToTZ(stdTime) - stdTime);
    }


    /++
        $(RED Please use either PosixTimeZone.getTimeZone or
              WindowsTimeZone.getTimeZone ($(LREF parseTZConversions) can be
              used to convert time zone names if necessary). This function will
              be deprecated in 2.072, because Microsoft changes their time zones
              too often for us to compile the conversions into Phobos and have
              them be properly up-to-date.)

        Returns a $(LREF2 .TimeZone, TimeZone) with the give name per the TZ Database.

        This returns a $(LREF PosixTimeZone) on Posix systems and a
        $(LREF WindowsTimeZone) on Windows systems. For
        $(LREF PosixTimeZone) on Windows, call $(D PosixTimeZone.getTimeZone)
        directly and give it the location of the TZ Database time zone files on
        disk.

        On Windows, the given TZ Database name is converted to the corresponding
        time zone name on Windows prior to calling
        $(D WindowsTimeZone.getTimeZone). This function allows for
        the same time zone names on both Windows and Posix systems.

        See_Also:
            $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
              Database)<br>
            $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
              Time Zones)<br>
            $(WEB unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html,
                  Windows <-> TZ Database Name Conversion Table)

        Params:
            name = The TZ Database name of the desired time zone

        Throws:
            $(LREF DateTimeException) if the given time zone could not be found.
      +/
    static immutable(TimeZone) getTimeZone(string name) @safe
    {
        version(Posix)
            return PosixTimeZone.getTimeZone(name);
        else version(Windows)
        {
            import std.format : format;
            auto windowsTZName = tzDatabaseNameToWindowsTZName(name);
            if(windowsTZName != null)
            {
                try
                    return WindowsTimeZone.getTimeZone(windowsTZName);
                catch(DateTimeException dte)
                {
                    auto oldName = _getOldName(windowsTZName);
                    if(oldName != null)
                        return WindowsTimeZone.getTimeZone(oldName);
                    throw dte;
                }
            }
            else
                throw new DateTimeException(format("%s does not have an equivalent Windows time zone.", name));
        }
    }

    ///
    unittest
    {
        auto tz = TimeZone.getTimeZone("America/Los_Angeles");
    }

    // The purpose of this is to handle the case where a Windows time zone is
    // new and exists on an up-to-date Windows box but does not exist on Windows
    // boxes which have not been properly updated. The "date added" is included
    // on the theory that we'll be able to remove them at some point in the
    // the future once enough time has passed, and that way, we know how much
    // time has passed.
    private static string _getOldName(string windowsTZName) @safe pure nothrow
    {
        switch(windowsTZName)
        {
            case "Belarus Standard Time": return "Kaliningrad Standard Time"; // Added 2014-10-08
            case "Russia Time Zone 10": return "Magadan Standard Time"; // Added 2014-10-08
            case "Russia Time Zone 11": return "Magadan Standard Time"; // Added 2014-10-08
            case "Russia Time Zone 3": return "Russian Standard Time"; // Added 2014-10-08
            default: return null;
        }
    }

    //Since reading in the time zone files could be expensive, most unit tests
    //are consolidated into this one unittest block which minimizes how often it
    //reads a time zone file.
    unittest
    {
        import std.path : chainPath;
        import std.file : exists, isFile;
        import std.conv : to;
        import std.format : format;


        version(Posix) scope(exit) clearTZEnvVar();

        static immutable(TimeZone) testTZ(string tzName,
                                          string stdName,
                                          string dstName,
                                          Duration utcOffset,
                                          Duration dstOffset,
                                          bool north = true)
        {
            scope(failure) writefln("Failed time zone: %s", tzName);

            immutable tz = TimeZone.getTimeZone(tzName);
            immutable hasDST = dstOffset != dur!"hnsecs"(0);

            version(Posix)
                assert(tz.name == tzName);
            else version(Windows)
                assert(tz.name == stdName);

            //assert(tz.stdName == stdName);  //Locale-dependent
            //assert(tz.dstName == dstName);  //Locale-dependent
            assert(tz.hasDST == hasDST);

            immutable stdDate = DateTime(2010, north ? 1 : 7, 1, 6, 0, 0);
            immutable dstDate = DateTime(2010, north ? 7 : 1, 1, 6, 0, 0);
            auto std = SysTime(stdDate, tz);
            auto dst = SysTime(dstDate, tz);
            auto stdUTC = SysTime(stdDate - utcOffset, UTC());
            auto dstUTC = SysTime(stdDate - utcOffset + dstOffset, UTC());

            assert(!std.dstInEffect);
            assert(dst.dstInEffect == hasDST);
            assert(tz.utcOffsetAt(std.stdTime) == utcOffset);
            assert(tz.utcOffsetAt(dst.stdTime) == utcOffset + dstOffset);

            assert(cast(DateTime)std == stdDate);
            assert(cast(DateTime)dst == dstDate);
            assert(std == stdUTC);

            version(Posix)
            {
                setTZEnvVar(tzName);

                static void testTM(in SysTime st)
                {
                    time_t unixTime = st.toUnixTime();
                    tm* osTimeInfo = localtime(&unixTime);
                    tm ourTimeInfo = st.toTM();

                    assert(ourTimeInfo.tm_sec == osTimeInfo.tm_sec);
                    assert(ourTimeInfo.tm_min == osTimeInfo.tm_min);
                    assert(ourTimeInfo.tm_hour == osTimeInfo.tm_hour);
                    assert(ourTimeInfo.tm_mday == osTimeInfo.tm_mday);
                    assert(ourTimeInfo.tm_mon == osTimeInfo.tm_mon);
                    assert(ourTimeInfo.tm_year == osTimeInfo.tm_year);
                    assert(ourTimeInfo.tm_wday == osTimeInfo.tm_wday);
                    assert(ourTimeInfo.tm_yday == osTimeInfo.tm_yday);
                    assert(ourTimeInfo.tm_isdst == osTimeInfo.tm_isdst);
                    assert(ourTimeInfo.tm_gmtoff == osTimeInfo.tm_gmtoff);
                    assert(to!string(ourTimeInfo.tm_zone) ==
                                     to!string(osTimeInfo.tm_zone));
                }

                testTM(std);
                testTM(dst);

                //Apparently, right/ does not exist on Mac OS X. I don't know
                //whether or not it exists on FreeBSD. It's rather pointless
                //normally, since the Posix standard requires that leap seconds
                //be ignored, so it does make some sense that right/ wouldn't
                //be there, but since PosixTimeZone _does_ use leap seconds if
                //the time zone file does, we'll test that functionality if the
                //appropriate files exist.
                if (chainPath(PosixTimeZone.defaultTZDatabaseDir, "right", tzName).exists)
                {
                    auto leapTZ = PosixTimeZone.getTimeZone("right/" ~ tzName);

                    assert(leapTZ.name == "right/" ~ tzName);
                    //assert(leapTZ.stdName == stdName);  //Locale-dependent
                    //assert(leapTZ.dstName == dstName);  //Locale-dependent
                    assert(leapTZ.hasDST == hasDST);

                    auto leapSTD = SysTime(std.stdTime, leapTZ);
                    auto leapDST = SysTime(dst.stdTime, leapTZ);

                    assert(!leapSTD.dstInEffect);
                    assert(leapDST.dstInEffect == hasDST);

                    assert(leapSTD.stdTime == std.stdTime);
                    assert(leapDST.stdTime == dst.stdTime);

                    //Whenever a leap second is added/removed,
                    //this will have to be adjusted.
                    //enum leapDiff = convert!("seconds", "hnsecs")(25);
                    //assert(leapSTD.adjTime - leapDiff == std.adjTime);
                    //assert(leapDST.adjTime - leapDiff == dst.adjTime);
                }
            }

            return tz;
        }

        auto dstSwitches = [/+America/Los_Angeles+/ tuple(DateTime(2012, 3, 11),  DateTime(2012, 11, 4), 2, 2),
                            /+America/New_York+/    tuple(DateTime(2012, 3, 11),  DateTime(2012, 11, 4), 2, 2),
                            ///+America/Santiago+/    tuple(DateTime(2011, 8, 21),  DateTime(2011, 5, 8), 0, 0),
                            /+Europe/London+/       tuple(DateTime(2012, 3, 25),  DateTime(2012, 10, 28), 1, 2),
                            /+Europe/Paris+/        tuple(DateTime(2012, 3, 25),  DateTime(2012, 10, 28), 2, 3),
                            /+Australia/Adelaide+/  tuple(DateTime(2012, 10, 7),  DateTime(2012, 4, 1), 2, 3)];

        version(Posix)
        {
            version(FreeBSD)      enum utcZone = "Etc/UTC";
            else version(NetBSD)  enum utcZone = "UTC";
            else version(linux)   enum utcZone = "UTC";
            else version(OSX)     enum utcZone = "UTC";
            else static assert(0, "The location of the UTC timezone file on this Posix platform must be set.");

            auto tzs = [testTZ("America/Los_Angeles", "PST", "PDT", dur!"hours"(-8), dur!"hours"(1)),
                        testTZ("America/New_York", "EST", "EDT", dur!"hours"(-5), dur!"hours"(1)),
                        //testTZ("America/Santiago", "CLT", "CLST", dur!"hours"(-4), dur!"hours"(1), false),
                        testTZ("Europe/London", "GMT", "BST", dur!"hours"(0), dur!"hours"(1)),
                        testTZ("Europe/Paris", "CET", "CEST", dur!"hours"(1), dur!"hours"(1)),
                        //Per www.timeanddate.com, it should be "CST" and "CDT",
                        //but the OS insists that it's "CST" for both. We should
                        //probably figure out how to report an error in the TZ
                        //database and report it.
                        testTZ("Australia/Adelaide", "CST", "CST",
                               dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)];

            testTZ(utcZone, "UTC", "UTC", dur!"hours"(0), dur!"hours"(0));
            assertThrown!DateTimeException(PosixTimeZone.getTimeZone("hello_world"));
        }
        else version(Windows)
        {
            auto tzs = [testTZ("America/Los_Angeles", "Pacific Standard Time",
                               "Pacific Daylight Time", dur!"hours"(-8), dur!"hours"(1)),
                        testTZ("America/New_York", "Eastern Standard Time",
                               "Eastern Daylight Time", dur!"hours"(-5), dur!"hours"(1)),
                        //testTZ("America/Santiago", "Pacific SA Standard Time",
                               //"Pacific SA Daylight Time", dur!"hours"(-4), dur!"hours"(1), false),
                        testTZ("Europe/London", "GMT Standard Time",
                               "GMT Daylight Time", dur!"hours"(0), dur!"hours"(1)),
                        testTZ("Europe/Paris", "Romance Standard Time",
                               "Romance Daylight Time", dur!"hours"(1), dur!"hours"(1)),
                        testTZ("Australia/Adelaide", "Cen. Australia Standard Time",
                               "Cen. Australia Daylight Time",
                               dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)];

            testTZ("Atlantic/Reykjavik", "Greenwich Standard Time",
                   "Greenwich Daylight Time", dur!"hours"(0), dur!"hours"(0));
            assertThrown!DateTimeException(WindowsTimeZone.getTimeZone("hello_world"));
        }
        else
            assert(0, "OS not supported.");

        foreach(i; 0 .. tzs.length)
        {
            auto tz = tzs[i];
            immutable spring = dstSwitches[i][2];
            immutable fall = dstSwitches[i][3];
            auto stdOffset = SysTime(dstSwitches[i][0] + dur!"days"(-1), tz).utcOffset;
            auto dstOffset = stdOffset + dur!"hours"(1);

            //Verify that creating a SysTime in the given time zone results
            //in a SysTime with the correct std time during and surrounding
            //a DST switch.
            foreach(hour; -12 .. 13)
            {
                auto st = SysTime(dstSwitches[i][0] + dur!"hours"(hour), tz);
                immutable targetHour = hour < 0 ? hour + 24 : hour;

                static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__)
                {
                    enforce(st.hour == hour,
                            new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour),
                                            __FILE__, line));
                }

                void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__)
                {
                    AssertError msg(string tag)
                    {
                        return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]",
                                                      tag, st, tz.name, st.utcOffset, stdOffset, dstOffset),
                                               __FILE__, line);
                    }

                    enforce(st.dstInEffect == dstInEffect, msg("1"));
                    enforce(st.utcOffset == offset, msg("2"));
                    enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3"));
                }

                if(hour == spring)
                {
                    testHour(st, spring + 1, tz.name);
                    testHour(st + dur!"minutes"(1), spring + 1, tz.name);
                }
                else
                {
                    testHour(st, targetHour, tz.name);
                    testHour(st + dur!"minutes"(1), targetHour, tz.name);
                }

                if(hour < spring)
                    testOffset1(stdOffset, false);
                else
                    testOffset1(dstOffset, true);

                st = SysTime(dstSwitches[i][1] + dur!"hours"(hour), tz);
                testHour(st, targetHour, tz.name);

                //Verify that 01:00 is the first 01:00 (or whatever hour before the switch is).
                if(hour == fall - 1)
                    testHour(st + dur!"hours"(1), targetHour, tz.name);

                    if(hour < fall)
                        testOffset1(dstOffset, true);
                    else
                        testOffset1(stdOffset, false);
            }

            //Verify that converting a time in UTC to a time in another
            //time zone results in the correct time during and surrounding
            //a DST switch.
            bool first = true;
            auto springSwitch = SysTime(dstSwitches[i][0] + dur!"hours"(spring), UTC()) - stdOffset;
            auto fallSwitch = SysTime(dstSwitches[i][1] + dur!"hours"(fall), UTC()) - dstOffset;
            //@@@BUG@@@ 3659 makes this necessary.
            auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1);

            foreach(hour; -24 .. 25)
            {
                auto utc = SysTime(dstSwitches[i][0] + dur!"hours"(hour), UTC());
                auto local = utc.toOtherTZ(tz);

                void testOffset2(Duration offset, size_t line = __LINE__)
                {
                    AssertError msg(string tag)
                    {
                        return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tz.name, utc, local),
                                               __FILE__, line);
                    }

                    enforce((utc + offset).hour == local.hour, msg("1"));
                    enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2"));
                }

                if(utc < springSwitch)
                    testOffset2(stdOffset);
                else
                    testOffset2(dstOffset);

                utc = SysTime(dstSwitches[i][1] + dur!"hours"(hour), UTC());
                local = utc.toOtherTZ(tz);

                if(utc == fallSwitch || utc == fallSwitchMinus1)
                {
                    if(first)
                    {
                        testOffset2(dstOffset);
                        first = false;
                    }
                    else
                        testOffset2(stdOffset);
                }
                else if(utc > fallSwitch)
                    testOffset2(stdOffset);
                else
                    testOffset2(dstOffset);
            }
        }
    }


    /++
        Returns a list of the names of the time zones installed on the system.

        Providing a sub-name narrows down the list of time zones (which
        can number in the thousands). For example,
        passing in "America" as the sub-name returns only the time zones which
        begin with "America".

        On Windows, this function will convert the Windows time zone names to
        the corresponding TZ Database names with
        $(D windowsTZNameToTZDatabaseName). To get the actual Windows time
        zone names, use $(D WindowsTimeZone.getInstalledTZNames) directly.

        Params:
            subName = The first part of the time zones desired.

        Throws:
            $(D FileException) on Posix systems if it fails to read from disk.
            $(LREF DateTimeException) on Windows systems if it fails to read the
            registry.
      +/
    static string[] getInstalledTZNames(string subName = "") @safe
    {
        version(Posix)
            return PosixTimeZone.getInstalledTZNames(subName);
        else version(Windows)
        {
            import std.array : appender;
            import std.algorithm : startsWith, sort;

            auto windowsNames = WindowsTimeZone.getInstalledTZNames();
            auto retval = appender!(string[])();

            foreach(winName; windowsNames)
            {
                auto tzName = windowsTZNameToTZDatabaseName(winName);
                if(tzName !is null && tzName.startsWith(subName))
                    retval.put(tzName);
            }

            sort(retval.data);
            return retval.data;
        }
    }

    unittest
    {
        static void testPZSuccess(string tzName)
        {
            scope(failure) writefln("TZName which threw: %s", tzName);
            TimeZone.getTimeZone(tzName);
        }

        auto tzNames = getInstalledTZNames();
        // This was not previously tested, and it's currently failing, so I'm
        // leaving it commented out until I can sort it out.
        //assert(equal(tzNames, tzNames.uniq()));

        foreach(tzName; tzNames)
            assertNotThrown!DateTimeException(testPZSuccess(tzName));
    }


private:

    /+
        Params:
            name    = The TZ Database name for the time zone.
            stdName = The abbreviation for the time zone during std time.
            dstName = The abbreviation for the time zone during DST.
      +/
    this(string name, string stdName, string dstName) @safe immutable pure
    {
        _name = name;
        _stdName = stdName;
        _dstName = dstName;
    }


    immutable string _name;
    immutable string _stdName;
    immutable string _dstName;
}


/++
    A TimeZone which represents the current local time zone on
    the system running your program.

    This uses the underlying C calls to adjust the time rather than using
    specific D code based off of system settings to calculate the time such as
    $(LREF PosixTimeZone) and $(LREF WindowsTimeZone) do. That also means that it will
    use whatever the current time zone is on the system, even if the system's
    time zone changes while the program is running.
  +/
final class LocalTime : TimeZone
{
public:

    /++
        $(LREF LocalTime) is a singleton class. $(LREF LocalTime) returns its only
        instance.
      +/
    static immutable(LocalTime) opCall() @trusted pure nothrow
    {
        alias FuncType = @safe pure nothrow immutable(LocalTime) function();
        return (cast(FuncType)&singleton)();
    }


    version(StdDdoc)
    {
        /++
            The name of the time zone per the TZ Database. This is the name used to
            get a $(LREF2 .TimeZone, TimeZone) by name with $(D TimeZone.getTimeZone).

            Note that this always returns the empty string. This is because time
            zones cannot be uniquely identified by the attributes given by the
            OS (such as the $(D stdName) and $(D dstName)), and neither Posix
            systems nor Windows systems provide an easy way to get the TZ
            Database name of the local time zone.

            See_Also:
                $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
                  Database)<br>
                $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List
                  of Time Zones)
          +/
        @property override string name() @safe const nothrow;
    }


    /++
        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
        when DST is $(I not) in effect (e.g. PST). It is not necessarily unique.

        However, on Windows, it may be the unabbreviated name (e.g. Pacific
        Standard Time). Regardless, it is not the same as name.

        This property is overridden because the local time of the system could
        change while the program is running and we need to determine it
        dynamically rather than it being fixed like it would be with most time
        zones.
      +/
    @property override string stdName() @trusted const nothrow
    {
        version(Posix)
        {
            import std.conv : to;
            try
                return to!string(tzname[0]);
            catch(Exception e)
                assert(0, "to!string(tzname[0]) failed.");
        }
        else version(Windows)
        {
            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            //Cannot use to!string() like this should, probably due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5016
            //return to!string(tzInfo.StandardName);

            wchar[32] str;

            foreach(i, ref wchar c; str)
                c = tzInfo.StandardName[i];

            string retval;

            try
            {
                foreach(dchar c; str)
                {
                    if(c == '\0')
                        break;

                    retval ~= c;
                }

                return retval;
            }
            catch(Exception e)
                assert(0, "GetTimeZoneInformation() returned invalid UTF-16.");
        }
    }

    unittest
{
        assert(LocalTime().stdName !is null);

        version(Posix)
        {
            scope(exit) clearTZEnvVar();

            setTZEnvVar("America/Los_Angeles");
            assert(LocalTime().stdName == "PST");

            setTZEnvVar("America/New_York");
            assert(LocalTime().stdName == "EST");
        }
    }


    /++
        Typically, the abbreviation (generally 3 or 4 letters) for the time zone
        when DST $(I is) in effect (e.g. PDT). It is not necessarily unique.

        However, on Windows, it may be the unabbreviated name (e.g. Pacific
        Daylight Time). Regardless, it is not the same as name.

        This property is overridden because the local time of the system could
        change while the program is running and we need to determine it
        dynamically rather than it being fixed like it would be with most time
        zones.
      +/
    @property override string dstName() @trusted const nothrow
    {
        version(Posix)
        {
            import std.conv : to;
            try
                return to!string(tzname[1]);
            catch(Exception e)
                assert(0, "to!string(tzname[1]) failed.");
        }
        else version(Windows)
        {
            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            //Cannot use to!string() like this should, probably due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5016
            //return to!string(tzInfo.DaylightName);

            wchar[32] str;

            foreach(i, ref wchar c; str)
                c = tzInfo.DaylightName[i];

            string retval;

            try
            {
                foreach(dchar c; str)
                {
                    if(c == '\0')
                        break;

                    retval ~= c;
                }

                return retval;
            }
            catch(Exception e)
                assert(0, "GetTimeZoneInformation() returned invalid UTF-16.");
        }
    }

    unittest
{
        assert(LocalTime().dstName !is null);

        version(Posix)
        {
            scope(exit) clearTZEnvVar();

            version(FreeBSD)
            {
                // A bug on FreeBSD 9+ makes it so that this test fails.
                // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=168862
            }
            else version(NetBSD)
            {
                // The same bug on NetBSD 7+
            }
            else
            {
                setTZEnvVar("America/Los_Angeles");
                assert(LocalTime().dstName == "PDT");
            }

            setTZEnvVar("America/New_York");
            assert(LocalTime().dstName == "EDT");
        }
    }


    /++
        Whether this time zone has Daylight Savings Time at any point in time.
        Note that for some time zone types it may not have DST for current
        dates but will still return true for $(D hasDST) because the time zone
        did at some point have DST.
      +/
    @property override bool hasDST() @trusted const nothrow
    {
        version(Posix)
        {
            static if(is(typeof(daylight)))
                return cast(bool)(daylight);
            else
            {
                try
                {
                    auto currYear = (cast(Date)Clock.currTime()).year;
                    auto janOffset = SysTime(Date(currYear, 1, 4), cast(immutable)this).stdTime -
                                     SysTime(Date(currYear, 1, 4), UTC()).stdTime;
                    auto julyOffset = SysTime(Date(currYear, 7, 4), cast(immutable)this).stdTime -
                                      SysTime(Date(currYear, 7, 4), UTC()).stdTime;

                    return janOffset != julyOffset;
                }
                catch(Exception e)
                    assert(0, "Clock.currTime() threw.");
            }
        }
        else version(Windows)
        {
            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            return tzInfo.DaylightDate.wMonth != 0;
        }
    }

    unittest
{
        LocalTime().hasDST;

        version(Posix)
        {
            scope(exit) clearTZEnvVar();

            setTZEnvVar("America/Los_Angeles");
            assert(LocalTime().hasDST);

            setTZEnvVar("America/New_York");
            assert(LocalTime().hasDST);

            setTZEnvVar("UTC");
            assert(!LocalTime().hasDST);
        }
    }


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in UTC time (i.e. std time) and returns whether DST is in effect in this
        time zone at the given point in time.

        Params:
            stdTime = The UTC time that needs to be checked for DST in this time
                      zone.
      +/
    override bool dstInEffect(long stdTime) @trusted const nothrow
    {
        time_t unixTime = stdTimeToUnixTime(stdTime);

        version(Posix)
        {
            tm* timeInfo = localtime(&unixTime);

            return cast(bool)(timeInfo.tm_isdst);
        }
        else version(Windows)
        {
            //Apparently Windows isn't smart enough to deal with negative time_t.
            if(unixTime >= 0)
            {
                tm* timeInfo = localtime(&unixTime);

                if(timeInfo)
                    return cast(bool)(timeInfo.tm_isdst);
            }

            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            return WindowsTimeZone._dstInEffect(&tzInfo, stdTime);
        }
    }

    unittest
    {
        auto currTime = Clock.currStdTime;
        LocalTime().dstInEffect(currTime);
    }


    /++
        Returns hnsecs in the local time zone using the standard C function
        calls on Posix systems and the standard Windows system calls on Windows
        systems to adjust the time to the appropriate time zone from std time.

        Params:
            stdTime = The UTC time that needs to be adjusted to this time zone's
                      time.

        See_Also:
            $(D TimeZone.utcToTZ)
      +/
    override long utcToTZ(long stdTime) @trusted const nothrow
    {
        version(Posix)
        {
            time_t unixTime = stdTimeToUnixTime(stdTime);
            tm* timeInfo = localtime(&unixTime);

            return stdTime + convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff);
        }
        else version(Windows)
        {
            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            return WindowsTimeZone._utcToTZ(&tzInfo, stdTime, hasDST);
        }
    }

    unittest
    {
        LocalTime().utcToTZ(0);
    }


    /++
        Returns std time using the standard C function calls on Posix systems
        and the standard Windows system calls on Windows systems to adjust the
        time to UTC from the appropriate time zone.

        See_Also:
            $(D TimeZone.tzToUTC)

        Params:
            adjTime = The time in this time zone that needs to be adjusted to
                      UTC time.
      +/
    override long tzToUTC(long adjTime) @trusted const nothrow
    {
        version(Posix)
        {
            time_t unixTime = stdTimeToUnixTime(adjTime);

            immutable past = unixTime - cast(time_t)convert!("days", "seconds")(1);
            tm* timeInfo = localtime(past < unixTime ? &past : &unixTime);
            immutable pastOffset = timeInfo.tm_gmtoff;

            immutable future = unixTime + cast(time_t)convert!("days", "seconds")(1);
            timeInfo = localtime(future > unixTime ? &future : &unixTime);
            immutable futureOffset = timeInfo.tm_gmtoff;

            if(pastOffset == futureOffset)
                return adjTime - convert!("seconds", "hnsecs")(pastOffset);

            if(pastOffset < futureOffset)
                unixTime -= cast(time_t)convert!("hours", "seconds")(1);

            unixTime -= pastOffset;
            timeInfo = localtime(&unixTime);

            return adjTime - convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff);
        }
        else version(Windows)
        {
            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            return WindowsTimeZone._tzToUTC(&tzInfo, adjTime, hasDST);
        }
    }

    unittest
    {
        import std.format : format;

        assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0);
        assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0);

        assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0);
        assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0);

        version(Posix)
        {
            scope(exit) clearTZEnvVar();

            auto tzInfos = [tuple("America/Los_Angeles", DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2),
                            tuple("America/New_York",    DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2),
                            //tuple("America/Santiago",    DateTime(2011, 8, 21), DateTime(2011, 5, 8), 0, 0),
                            tuple("Atlantic/Azores",     DateTime(2011, 3, 27), DateTime(2011, 10, 30), 0, 1),
                            tuple("Europe/London",       DateTime(2012, 3, 25), DateTime(2012, 10, 28), 1, 2),
                            tuple("Europe/Paris",        DateTime(2012, 3, 25), DateTime(2012, 10, 28), 2, 3),
                            tuple("Australia/Adelaide",  DateTime(2012, 10, 7), DateTime(2012, 4, 1), 2, 3)];

            foreach(i; 0 .. tzInfos.length)
            {
                auto tzName = tzInfos[i][0];
                setTZEnvVar(tzName);
                immutable spring = tzInfos[i][3];
                immutable fall = tzInfos[i][4];
                auto stdOffset = SysTime(tzInfos[i][1] + dur!"hours"(-12)).utcOffset;
                auto dstOffset = stdOffset + dur!"hours"(1);

                //Verify that creating a SysTime in the given time zone results
                //in a SysTime with the correct std time during and surrounding
                //a DST switch.
                foreach(hour; -12 .. 13)
                {
                    auto st = SysTime(tzInfos[i][1] + dur!"hours"(hour));
                    immutable targetHour = hour < 0 ? hour + 24 : hour;

                    static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__)
                    {
                        enforce(st.hour == hour,
                                new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour),
                                                __FILE__, line));
                    }

                    void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__)
                    {
                        AssertError msg(string tag)
                        {
                            return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]",
                                                          tag, st, tzName, st.utcOffset, stdOffset, dstOffset),
                                                   __FILE__, line);
                        }

                        enforce(st.dstInEffect == dstInEffect, msg("1"));
                        enforce(st.utcOffset == offset, msg("2"));
                        enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3"));
                    }

                    if(hour == spring)
                    {
                        testHour(st, spring + 1, tzName);
                        testHour(st + dur!"minutes"(1), spring + 1, tzName);
                    }
                    else
                    {
                        testHour(st, targetHour, tzName);
                        testHour(st + dur!"minutes"(1), targetHour, tzName);
                    }

                    if(hour < spring)
                        testOffset1(stdOffset, false);
                    else
                        testOffset1(dstOffset, true);

                    st = SysTime(tzInfos[i][2] + dur!"hours"(hour));
                    testHour(st, targetHour, tzName);

                    //Verify that 01:00 is the first 01:00 (or whatever hour before the switch is).
                    if(hour == fall - 1)
                        testHour(st + dur!"hours"(1), targetHour, tzName);

                    if(hour < fall)
                        testOffset1(dstOffset, true);
                    else
                        testOffset1(stdOffset, false);
                }

                //Verify that converting a time in UTC to a time in another
                //time zone results in the correct time during and surrounding
                //a DST switch.
                bool first = true;
                auto springSwitch = SysTime(tzInfos[i][1] + dur!"hours"(spring), UTC()) - stdOffset;
                auto fallSwitch = SysTime(tzInfos[i][2] + dur!"hours"(fall), UTC()) - dstOffset;
                //@@@BUG@@@ 3659 makes this necessary.
                auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1);

                foreach(hour; -24 .. 25)
                {
                    auto utc = SysTime(tzInfos[i][1] + dur!"hours"(hour), UTC());
                    auto local = utc.toLocalTime();

                    void testOffset2(Duration offset, size_t line = __LINE__)
                    {
                        AssertError msg(string tag)
                        {
                            return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tzName, utc, local),
                                                   __FILE__, line);
                        }

                        enforce((utc + offset).hour == local.hour, msg("1"));
                        enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2"));
                    }

                    if(utc < springSwitch)
                        testOffset2(stdOffset);
                    else
                        testOffset2(dstOffset);

                    utc = SysTime(tzInfos[i][2] + dur!"hours"(hour), UTC());
                    local = utc.toLocalTime();

                    if(utc == fallSwitch || utc == fallSwitchMinus1)
                    {
                        if(first)
                        {
                            testOffset2(dstOffset);
                            first = false;
                        }
                        else
                            testOffset2(stdOffset);
                    }
                    else if(utc > fallSwitch)
                        testOffset2(stdOffset);
                    else
                        testOffset2(dstOffset);
                }
            }
        }
    }


private:

    this() @safe immutable pure
    {
        super("", "", "");
    }


    // This is done so that we can maintain purity in spite of doing an impure
    // operation the first time that LocalTime() is called.
    static immutable(LocalTime) singleton() @trusted
    {
        import std.concurrency : initOnce;
        static instance = new immutable(LocalTime)();
        static shared bool guard;
        initOnce!guard({tzset(); return true;}());
        return instance;
    }
}


/++
    A $(LREF2 .TimeZone, TimeZone) which represents UTC.
  +/
final class UTC : TimeZone
{
public:

    /++
        $(D UTC) is a singleton class. $(D UTC) returns its only instance.
      +/
    static immutable(UTC) opCall() @safe pure nothrow
    {
        return _utc;
    }


    /++
        Always returns false.
      +/
    @property override bool hasDST() @safe const nothrow
    {
        return false;
    }


    /++
        Always returns false.
      +/
    override bool dstInEffect(long stdTime) @safe const nothrow
    {
        return false;
    }


    /++
        Returns the given hnsecs without changing them at all.

        Params:
            stdTime = The UTC time that needs to be adjusted to this time zone's
                      time.

        See_Also:
            $(D TimeZone.utcToTZ)
      +/
    override long utcToTZ(long stdTime) @safe const nothrow
    {
        return stdTime;
    }

    unittest
    {
        assert(UTC().utcToTZ(0) == 0);

        version(Posix)
        {
            scope(exit) clearTZEnvVar();

            setTZEnvVar("UTC");
            auto std = SysTime(Date(2010, 1, 1));
            auto dst = SysTime(Date(2010, 7, 1));
            assert(UTC().utcToTZ(std.stdTime) == std.stdTime);
            assert(UTC().utcToTZ(dst.stdTime) == dst.stdTime);
        }
    }


    /++
        Returns the given hnsecs without changing them at all.

        See_Also:
            $(D TimeZone.tzToUTC)

        Params:
            adjTime = The time in this time zone that needs to be adjusted to
                      UTC time.
      +/
    override long tzToUTC(long adjTime) @safe const nothrow
    {
        return adjTime;
    }

    unittest
    {
        assert(UTC().tzToUTC(0) == 0);

        version(Posix)
        {
            scope(exit) clearTZEnvVar();

            setTZEnvVar("UTC");
            auto std = SysTime(Date(2010, 1, 1));
            auto dst = SysTime(Date(2010, 7, 1));
            assert(UTC().tzToUTC(std.stdTime) == std.stdTime);
            assert(UTC().tzToUTC(dst.stdTime) == dst.stdTime);
        }
    }


    /++
        Returns a $(CXREF time, Duration) of 0.

        Params:
            stdTime = The UTC time for which to get the offset from UTC for this
                      time zone.
      +/
    override Duration utcOffsetAt(long stdTime) @safe const nothrow
    {
        return dur!"hnsecs"(0);
    }


private:

    this() @safe immutable pure
    {
        super("UTC", "UTC", "UTC");
    }


    static immutable UTC _utc = new immutable(UTC)();
}


/++
    Represents a time zone with an offset (in minutes, west is negative) from
    UTC but no DST.

    It's primarily used as the time zone in the result of $(LREF SysTime)'s
    $(D fromISOString), $(D fromISOExtString), and $(D fromSimpleString).

    $(D name) and $(D dstName) are always the empty string since this time zone
    has no DST, and while it may be meant to represent a time zone which is in
    the TZ Database, obviously it's not likely to be following the exact rules
    of any of the time zones in the TZ Database, so it makes no sense to set it.
  +/
final class SimpleTimeZone : TimeZone
{
public:

    /++
        Always returns false.
      +/
    @property override bool hasDST() @safe const nothrow
    {
        return false;
    }


    /++
        Always returns false.
      +/
    override bool dstInEffect(long stdTime) @safe const nothrow
    {
        return false;
    }


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in UTC time (i.e. std time) and converts it to this time zone's time.

        Params:
            stdTime = The UTC time that needs to be adjusted to this time zone's
                      time.
      +/
    override long utcToTZ(long stdTime) @safe const nothrow
    {
        return stdTime + _utcOffset.total!"hnsecs";
    }

    unittest
    {
        auto west = new immutable SimpleTimeZone(dur!"hours"(-8));
        auto east = new immutable SimpleTimeZone(dur!"hours"(8));

        assert(west.utcToTZ(0) == -288_000_000_000L);
        assert(east.utcToTZ(0) == 288_000_000_000L);
        assert(west.utcToTZ(54_321_234_567_890L) == 54_033_234_567_890L);

        const cstz = west;
        assert(cstz.utcToTZ(50002) == west.utcToTZ(50002));
    }


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in this time zone's time and converts it to UTC (i.e. std time).

        Params:
            adjTime = The time in this time zone that needs to be adjusted to
                      UTC time.
      +/
    override long tzToUTC(long adjTime) @safe const nothrow
    {
        return adjTime - _utcOffset.total!"hnsecs";
    }

    unittest
    {
        auto west = new immutable SimpleTimeZone(dur!"hours"(-8));
        auto east = new immutable SimpleTimeZone(dur!"hours"(8));

        assert(west.tzToUTC(-288_000_000_000L) == 0);
        assert(east.tzToUTC(288_000_000_000L) == 0);
        assert(west.tzToUTC(54_033_234_567_890L) == 54_321_234_567_890L);

        const cstz = west;
        assert(cstz.tzToUTC(20005) == west.tzToUTC(20005));
    }


    /++
        Returns utcOffset as a $(CXREF time, Duration).

        Params:
            stdTime = The UTC time for which to get the offset from UTC for this
                      time zone.
      +/
    override Duration utcOffsetAt(long stdTime) @safe const nothrow
    {
        return _utcOffset;
    }


    /++
        Params:
            utcOffset = This time zone's offset from UTC with west of UTC being
                        negative (it is added to UTC to get the adjusted time).
            stdName   = The $(D stdName) for this time zone.
      +/
    this(Duration utcOffset, string stdName = "") @safe immutable pure
    {
        //FIXME This probably needs to be changed to something like (-12 - 13).
        enforce!DateTimeException(abs(utcOffset) < dur!"minutes"(1440),
                                    "Offset from UTC must be within range (-24:00 - 24:00).");

        super("", stdName, "");
        this._utcOffset = utcOffset;
    }

    // Explicitly undocumented. It will be removed in August 2016. @@@DEPRECATED_2016-08@@@
    deprecated("Please use the overload which takes a Duration.")
    this(int utcOffset, string stdName = "") @safe immutable pure
    {
        this(dur!"minutes"(utcOffset), stdName);
    }

    unittest
    {
        auto stz = new immutable SimpleTimeZone(dur!"hours"(-8), "PST");
        assert(stz.name == "");
        assert(stz.stdName == "PST");
        assert(stz.dstName == "");
        assert(stz.utcOffset == dur!"hours"(-8));
    }


    /++
        The amount of time the offset from UTC is (negative is west of UTC,
        positive is east).
      +/
    @property Duration utcOffset() @safe const pure nothrow
    {
        return _utcOffset;
    }


private:

    /+
        Returns a time zone as a string with an offset from UTC.

        Time zone offsets will be in the form +HHMM or -HHMM.

        Params:
            utcOffset = The number of minutes offset from UTC (negative means
                        west).
      +/
    static string toISOString(Duration utcOffset) @safe pure
    {
        import std.format : format;
        immutable absOffset = abs(utcOffset);
        enforce!DateTimeException(absOffset < dur!"minutes"(1440),
                                  "Offset from UTC must be within range (-24:00 - 24:00).");
        int hours;
        int minutes;
        absOffset.split!("hours", "minutes")(hours, minutes);
        return format(utcOffset < Duration.zero ? "-%02d%02d" : "+%02d%02d", hours, minutes);
    }

    unittest
    {
        static string testSTZInvalid(Duration offset)
        {
            return SimpleTimeZone.toISOString(offset);
        }

        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440)));
        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440)));

        assert(toISOString(dur!"minutes"(0)) == "+0000");
        assert(toISOString(dur!"minutes"(1)) == "+0001");
        assert(toISOString(dur!"minutes"(10)) == "+0010");
        assert(toISOString(dur!"minutes"(59)) == "+0059");
        assert(toISOString(dur!"minutes"(60)) == "+0100");
        assert(toISOString(dur!"minutes"(90)) == "+0130");
        assert(toISOString(dur!"minutes"(120)) == "+0200");
        assert(toISOString(dur!"minutes"(480)) == "+0800");
        assert(toISOString(dur!"minutes"(1439)) == "+2359");

        assert(toISOString(dur!"minutes"(-1)) == "-0001");
        assert(toISOString(dur!"minutes"(-10)) == "-0010");
        assert(toISOString(dur!"minutes"(-59)) == "-0059");
        assert(toISOString(dur!"minutes"(-60)) == "-0100");
        assert(toISOString(dur!"minutes"(-90)) == "-0130");
        assert(toISOString(dur!"minutes"(-120)) == "-0200");
        assert(toISOString(dur!"minutes"(-480)) == "-0800");
        assert(toISOString(dur!"minutes"(-1439)) == "-2359");
    }


    /+
        Returns a time zone as a string with an offset from UTC.

        Time zone offsets will be in the form +HH:MM or -HH:MM.

        Params:
            utcOffset = The number of minutes offset from UTC (negative means
                        west).
      +/
    static string toISOExtString(Duration utcOffset) @safe pure
    {
        import std.format : format;

        immutable absOffset = abs(utcOffset);
        enforce!DateTimeException(absOffset < dur!"minutes"(1440),
                                  "Offset from UTC must be within range (-24:00 - 24:00).");
        int hours;
        int minutes;
        absOffset.split!("hours", "minutes")(hours, minutes);
        return format(utcOffset < Duration.zero ? "-%02d:%02d" : "+%02d:%02d", hours, minutes);
    }

    unittest
    {
        static string testSTZInvalid(Duration offset)
        {
            return SimpleTimeZone.toISOExtString(offset);
        }

        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440)));
        assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440)));

        assert(toISOExtString(dur!"minutes"(0)) == "+00:00");
        assert(toISOExtString(dur!"minutes"(1)) == "+00:01");
        assert(toISOExtString(dur!"minutes"(10)) == "+00:10");
        assert(toISOExtString(dur!"minutes"(59)) == "+00:59");
        assert(toISOExtString(dur!"minutes"(60)) == "+01:00");
        assert(toISOExtString(dur!"minutes"(90)) == "+01:30");
        assert(toISOExtString(dur!"minutes"(120)) == "+02:00");
        assert(toISOExtString(dur!"minutes"(480)) == "+08:00");
        assert(toISOExtString(dur!"minutes"(1439)) == "+23:59");

        assert(toISOExtString(dur!"minutes"(-1)) == "-00:01");
        assert(toISOExtString(dur!"minutes"(-10)) == "-00:10");
        assert(toISOExtString(dur!"minutes"(-59)) == "-00:59");
        assert(toISOExtString(dur!"minutes"(-60)) == "-01:00");
        assert(toISOExtString(dur!"minutes"(-90)) == "-01:30");
        assert(toISOExtString(dur!"minutes"(-120)) == "-02:00");
        assert(toISOExtString(dur!"minutes"(-480)) == "-08:00");
        assert(toISOExtString(dur!"minutes"(-1439)) == "-23:59");
    }


    /+
        Takes a time zone as a string with an offset from UTC and returns a
        $(LREF SimpleTimeZone) which matches.

        The accepted formats for time zone offsets are +HH, -HH, +HHMM, and
        -HHMM.

        Params:
            isoString = A string which represents a time zone in the ISO format.
      +/
    static immutable(SimpleTimeZone) fromISOString(S)(S isoString) @safe pure
        if(isSomeString!S)
    {
        import std.ascii : isDigit;
        import std.conv : to;
        import std.algorithm : startsWith, countUntil, all;
        import std.format : format;

        auto dstr = to!dstring(isoString);

        enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String");

        auto sign = dstr.startsWith('-') ? -1 : 1;

        dstr.popFront();
        enforce!DateTimeException(all!isDigit(dstr), format("Invalid ISO String: %s", dstr));

        int hours;
        int minutes;

        if(dstr.length == 2)
            hours = to!int(dstr);
        else if(dstr.length == 4)
        {
            hours = to!int(dstr[0 .. 2]);
            minutes = to!int(dstr[2 .. 4]);
        }
        else
            throw new DateTimeException(format("Invalid ISO String: %s", dstr));

        enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr));

        return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes)));
    }

    unittest
    {
        import std.format : format;

        foreach(str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1",
                      "-24:00", "+24:00", "-24", "+24", "-2400", "+2400",
                      "1", "+1", "-1", "+9", "-9",
                      "+1:0", "+01:0", "+1:00", "+01:000", "+01:60",
                      "-1:0", "-01:0", "-1:00", "-01:000", "-01:60",
                      "000", "00000", "0160", "-0160",
                      " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ",
                      " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ",
                      " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ",
                      " -0800", "- 0800", "-08 00", "-08 00", "-0800 ",
                      "+ab:cd", "+abcd", "+0Z:00", "+Z", "+00Z",
                      "-ab:cd", "+abcd", "-0Z:00", "-Z", "-00Z",
                      "01:00", "12:00", "23:59"])
        {
            assertThrown!DateTimeException(SimpleTimeZone.fromISOString(str), format("[%s]", str));
        }

        static void test(string str, Duration utcOffset, size_t line = __LINE__)
        {
            if(SimpleTimeZone.fromISOString(str).utcOffset !=
               (new immutable SimpleTimeZone(utcOffset)).utcOffset)
            {
                throw new AssertError("unittest failure", __FILE__, line);
            }
        }

        test("+0000", Duration.zero);
        test("+0001", minutes(1));
        test("+0010", minutes(10));
        test("+0059", minutes(59));
        test("+0100", hours(1));
        test("+0130", hours(1) + minutes(30));
        test("+0200", hours(2));
        test("+0800", hours(8));
        test("+2359", hours(23) + minutes(59));

        test("-0001", minutes(-1));
        test("-0010", minutes(-10));
        test("-0059", minutes(-59));
        test("-0100", hours(-1));
        test("-0130", hours(-1) - minutes(30));
        test("-0200", hours(-2));
        test("-0800", hours(-8));
        test("-2359", hours(-23) - minutes(59));

        test("+00", Duration.zero);
        test("+01", hours(1));
        test("+02", hours(2));
        test("+12", hours(12));
        test("+23", hours(23));

        test("-00", Duration.zero);
        test("-01", hours(-1));
        test("-02", hours(-2));
        test("-12", hours(-12));
        test("-23", hours(-23));
    }

    unittest
    {
        import std.format : format;

        static void test(in string isoString, int expectedOffset, size_t line = __LINE__)
        {
            auto stz = SimpleTimeZone.fromISOExtString(isoString);
            if(stz.utcOffset != dur!"minutes"(expectedOffset))
                throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line);

            auto result = SimpleTimeZone.toISOExtString(stz.utcOffset);
            if(result != isoString)
                throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoString), __FILE__, line);
        }

        test("+00:00", 0);
        test("+00:01", 1);
        test("+00:10", 10);
        test("+00:59", 59);
        test("+01:00", 60);
        test("+01:30", 90);
        test("+02:00", 120);
        test("+08:00", 480);
        test("+08:00", 480);
        test("+23:59", 1439);

        test("-00:01", -1);
        test("-00:10", -10);
        test("-00:59", -59);
        test("-01:00", -60);
        test("-01:30", -90);
        test("-02:00", -120);
        test("-08:00", -480);
        test("-08:00", -480);
        test("-23:59", -1439);
    }


    /+
        Takes a time zone as a string with an offset from UTC and returns a
        $(LREF SimpleTimeZone) which matches.

        The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
        -HH:MM.

        Params:
            isoExtString = A string which represents a time zone in the ISO format.
      +/
    static immutable(SimpleTimeZone) fromISOExtString(S)(S isoExtString) @safe pure
        if(isSomeString!S)
    {
        import std.ascii : isDigit;
        import std.conv : to;
        import std.algorithm : startsWith, countUntil, all;
        import std.format : format;

        auto dstr = to!dstring(isoExtString);

        enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String");

        auto sign = dstr.startsWith('-') ? -1 : 1;

        dstr.popFront();
        enforce!DateTimeException(!dstr.empty, "Invalid ISO String");

        immutable colon = dstr.countUntil(':');

        dstring hoursStr;
        dstring minutesStr;

        if(colon != -1)
        {
            hoursStr = dstr[0 .. colon];
            minutesStr = dstr[colon + 1 .. $];
            enforce!DateTimeException(minutesStr.length == 2, format("Invalid ISO String: %s", dstr));
        }
        else
            hoursStr = dstr;

        enforce!DateTimeException(hoursStr.length == 2, format("Invalid ISO String: %s", dstr));
        enforce!DateTimeException(all!isDigit(hoursStr), format("Invalid ISO String: %s", dstr));
        enforce!DateTimeException(all!isDigit(minutesStr), format("Invalid ISO String: %s", dstr));

        immutable hours = to!int(hoursStr);
        immutable minutes = minutesStr.empty ? 0 : to!int(minutesStr);
        enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr));

        return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes)));
    }

    unittest
    {
        import std.format : format;

        foreach(str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1",
                      "-24:00", "+24:00", "-24", "+24", "-2400", "-2400",
                      "1", "+1", "-1", "+9", "-9",
                      "+1:0", "+01:0", "+1:00", "+01:000", "+01:60",
                      "-1:0", "-01:0", "-1:00", "-01:000", "-01:60",
                      "000", "00000", "0160", "-0160",
                      " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ",
                      " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ",
                      " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ",
                      " -0800", "- 0800", "-08 00", "-08 00", "-0800 ",
                      "+ab:cd", "abcd", "+0Z:00", "+Z", "+00Z",
                      "-ab:cd", "abcd", "-0Z:00", "-Z", "-00Z",
                      "0100", "1200", "2359"])
        {
            assertThrown!DateTimeException(SimpleTimeZone.fromISOExtString(str), format("[%s]", str));
        }

        static void test(string str, Duration utcOffset, size_t line = __LINE__)
        {
            if(SimpleTimeZone.fromISOExtString(str).utcOffset !=
               (new immutable SimpleTimeZone(utcOffset)).utcOffset)
            {
                throw new AssertError("unittest failure", __FILE__, line);
            }
        }

        test("+00:00", Duration.zero);
        test("+00:01", minutes(1));
        test("+00:10", minutes(10));
        test("+00:59", minutes(59));
        test("+01:00", hours(1));
        test("+01:30", hours(1) + minutes(30));
        test("+02:00", hours(2));
        test("+08:00", hours(8));
        test("+23:59", hours(23) + minutes(59));

        test("-00:01", minutes(-1));
        test("-00:10", minutes(-10));
        test("-00:59", minutes(-59));
        test("-01:00", hours(-1));
        test("-01:30", hours(-1) - minutes(30));
        test("-02:00", hours(-2));
        test("-08:00", hours(-8));
        test("-23:59", hours(-23) - minutes(59));

        test("+00", Duration.zero);
        test("+01", hours(1));
        test("+02", hours(2));
        test("+12", hours(12));
        test("+23", hours(23));

        test("-00", Duration.zero);
        test("-01", hours(-1));
        test("-02", hours(-2));
        test("-12", hours(-12));
        test("-23", hours(-23));
    }

    unittest
    {
        import std.format : format;

        static void test(in string isoExtString, int expectedOffset, size_t line = __LINE__)
        {
            auto stz = SimpleTimeZone.fromISOExtString(isoExtString);
            if(stz.utcOffset != dur!"minutes"(expectedOffset))
                throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line);

            auto result = SimpleTimeZone.toISOExtString(stz.utcOffset);
            if(result != isoExtString)
                throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoExtString), __FILE__, line);
        }

        test("+00:00", 0);
        test("+00:01", 1);
        test("+00:10", 10);
        test("+00:59", 59);
        test("+01:00", 60);
        test("+01:30", 90);
        test("+02:00", 120);
        test("+08:00", 480);
        test("+08:00", 480);
        test("+23:59", 1439);

        test("-00:01", -1);
        test("-00:10", -10);
        test("-00:59", -59);
        test("-01:00", -60);
        test("-01:30", -90);
        test("-02:00", -120);
        test("-08:00", -480);
        test("-08:00", -480);
        test("-23:59", -1439);
    }


    immutable Duration _utcOffset;
}


/++
    Represents a time zone from a TZ Database time zone file. Files from the TZ
    Database are how Posix systems hold their time zone information.
    Unfortunately, Windows does not use the TZ Database. To use the TZ Database,
    use $(D PosixTimeZone) (which reads its information from the TZ Database
    files on disk) on Windows by providing the TZ Database files and telling
    $(D PosixTimeZone.getTimeZone) where the directory holding them is.

    To get a $(D PosixTimeZone), either call $(D PosixTimeZone.getTimeZone)
    (which allows specifying the location the time zone files) or call
    $(D TimeZone.getTimeZone) (which will give a $(D PosixTimeZone) on Posix
    systems and a $(LREF WindowsTimeZone) on Windows systems).

    Note:
        Unless your system's local time zone deals with leap seconds (which is
        highly unlikely), then the only way to get a time zone which
        takes leap seconds into account is to use $(D PosixTimeZone) with a
        time zone whose name starts with "right/". Those time zone files do
        include leap seconds, and $(D PosixTimeZone) will take them into account
        (though posix systems which use a "right/" time zone as their local time
        zone will $(I not) take leap seconds into account even though they're
        in the file).

    See_Also:
        $(WEB www.iana.org/time-zones, Home of the TZ Database files)<br>
        $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ Database)<br>
        $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of Time
          Zones)
  +/
final class PosixTimeZone : TimeZone
{
    import std.stdio : File;
    import std.path : extension;
    import std.file : isDir, isFile, exists, dirEntries, SpanMode, DirEntry;
    import std.string : strip, representation;
    import std.algorithm : countUntil, canFind, startsWith;
public:

    /++
        Whether this time zone has Daylight Savings Time at any point in time.
        Note that for some time zone types it may not have DST for current
        dates but will still return true for $(D hasDST) because the time zone
        did at some point have DST.
      +/
    @property override bool hasDST() @safe const nothrow
    {
        return _hasDST;
    }


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in UTC time (i.e. std time) and returns whether DST is in effect in this
        time zone at the given point in time.

        Params:
            stdTime = The UTC time that needs to be checked for DST in this time
                      zone.
      +/
    override bool dstInEffect(long stdTime) @safe const nothrow
    {
        assert(!_transitions.empty);

        immutable unixTime = stdTimeToUnixTime(stdTime);
        immutable found = countUntil!"b < a.timeT"(_transitions, unixTime);

        if(found == -1)
            return _transitions.back.ttInfo.isDST;

        immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1];

        return transition.ttInfo.isDST;
    }


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in UTC time (i.e. std time) and converts it to this time zone's time.

        Params:
            stdTime = The UTC time that needs to be adjusted to this time zone's
                      time.
      +/
    override long utcToTZ(long stdTime) @safe const nothrow
    {
        assert(!_transitions.empty);

        immutable leapSecs = calculateLeapSeconds(stdTime);
        immutable unixTime = stdTimeToUnixTime(stdTime);
        immutable found = countUntil!"b < a.timeT"(_transitions, unixTime);

        if(found == -1)
            return stdTime + convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);

        immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1];

        return stdTime + convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs);
    }


    /++
        Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
        in this time zone's time and converts it to UTC (i.e. std time).

        Params:
            adjTime = The time in this time zone that needs to be adjusted to
                      UTC time.
      +/
    override long tzToUTC(long adjTime) @safe const nothrow
    {
        assert(!_transitions.empty);

        immutable leapSecs = calculateLeapSeconds(adjTime);
        time_t unixTime = stdTimeToUnixTime(adjTime);
        immutable past = unixTime - convert!("days", "seconds")(1);
        immutable future = unixTime + convert!("days", "seconds")(1);

        immutable pastFound = countUntil!"b < a.timeT"(_transitions, past);

        if(pastFound == -1)
            return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);

        immutable futureFound = countUntil!"b < a.timeT"(_transitions[pastFound .. $], future);
        immutable pastTrans = pastFound == 0 ? _transitions[0] : _transitions[pastFound - 1];

        if(futureFound == 0)
            return adjTime - convert!("seconds", "hnsecs")(pastTrans.ttInfo.utcOffset + leapSecs);

        immutable futureTrans = futureFound == -1 ? _transitions.back
                                                  : _transitions[pastFound + futureFound - 1];
        immutable pastOffset = pastTrans.ttInfo.utcOffset;

        if(pastOffset < futureTrans.ttInfo.utcOffset)
            unixTime -= convert!("hours", "seconds")(1);

        immutable found = countUntil!"b < a.timeT"(_transitions[pastFound .. $], unixTime - pastOffset);

        if(found == -1)
            return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);

        immutable transition = found == 0 ? pastTrans : _transitions[pastFound + found - 1];

        return adjTime - convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs);
    }


    version(Android)
    {
        // Android concatenates all time zone data into a single file and stores it here.
        enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/";
    }
    else version(Posix)
    {
        /++
            The default directory where the TZ Database files are. It's empty
            for Windows, since Windows doesn't have them.
          +/
        enum defaultTZDatabaseDir = "/usr/share/zoneinfo/";
    }
    else version(Windows)
    {
        /++ The default directory where the TZ Database files are. It's empty
            for Windows, since Windows doesn't have them.
          +/
        enum defaultTZDatabaseDir = "";
    }


    /++
        Returns a $(LREF2 .TimeZone, TimeZone) with the give name per the TZ Database. The time
        zone information is fetched from the TZ Database time zone files in the
        given directory.

        See_Also:
            $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
              Database)<br>
            $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
              Time Zones)

        Params:
            name          = The TZ Database name of the desired time zone
            tzDatabaseDir = The directory where the TZ Database files are
                            located. Because these files are not located on
                            Windows systems, provide them
                            and give their location here to
                            use $(LREF PosixTimeZone)s.

        Throws:
            $(LREF DateTimeException) if the given time zone could not be found or
            $(D FileException) if the TZ Database file could not be opened.
      +/
    //TODO make it possible for tzDatabaseDir to be gzipped tar file rather than an uncompressed
    //     directory.
    static immutable(PosixTimeZone) getTimeZone(string name, string tzDatabaseDir = defaultTZDatabaseDir) @trusted
    {
        import std.algorithm : sort;
        import std.range : retro;
        import std.format : format;
        import std.path : asNormalizedPath, chainPath;
        import std.conv : to;

        name = strip(name);

        enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir)));
        enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir)));

        version(Android)
        {
            auto tzfileOffset = name in tzdataIndex(tzDatabaseDir);
            enforce(tzfileOffset, new DateTimeException(format("The time zone %s is not listed.", name)));
            string tzFilename = separate_index ? "zoneinfo.dat" : "tzdata";
            const file = asNormalizedPath(chainPath(tzDatabaseDir, tzFilename)).to!string;
        }
        else
            const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string;

        enforce(file.exists(), new DateTimeException(format("File %s does not exist.", file)));
        enforce(file.isFile, new DateTimeException(format("%s is not a file.", file)));

        auto tzFile = File(file);
        version(Android) tzFile.seek(*tzfileOffset);
        immutable gmtZone = name.representation().canFind("GMT");

        try
        {
            _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif");

            immutable char tzFileVersion = readVal!char(tzFile);
            _enforceValidTZFile(tzFileVersion == '\0' || tzFileVersion == '2' || tzFileVersion == '3');

            {
                auto zeroBlock = readVal!(ubyte[])(tzFile, 15);
                bool allZeroes = true;

                foreach(val; zeroBlock)
                {
                    if(val != 0)
                    {
                        allZeroes = false;
                        break;
                    }
                }

                _enforceValidTZFile(allZeroes);
            }


            //The number of UTC/local indicators stored in the file.
            auto tzh_ttisgmtcnt = readVal!int(tzFile);

            //The number of standard/wall indicators stored in the file.
            auto tzh_ttisstdcnt = readVal!int(tzFile);

            //The number of leap seconds for which data is stored in the file.
            auto tzh_leapcnt = readVal!int(tzFile);

            //The number of "transition times" for which data is stored in the file.
            auto tzh_timecnt = readVal!int(tzFile);

            //The number of "local time types" for which data is stored in the file (must not be zero).
            auto tzh_typecnt = readVal!int(tzFile);
            _enforceValidTZFile(tzh_typecnt != 0);

            //The number of characters of "timezone abbreviation strings" stored in the file.
            auto tzh_charcnt = readVal!int(tzFile);

            //time_ts where DST transitions occur.
            auto transitionTimeTs = new long[](tzh_timecnt);
            foreach(ref transition; transitionTimeTs)
                transition = readVal!int(tzFile);

            //Indices into ttinfo structs indicating the changes
            //to be made at the corresponding DST transition.
            auto ttInfoIndices = new ubyte[](tzh_timecnt);
            foreach(ref ttInfoIndex; ttInfoIndices)
                ttInfoIndex = readVal!ubyte(tzFile);

            //ttinfos which give info on DST transitions.
            auto tempTTInfos = new TempTTInfo[](tzh_typecnt);
            foreach(ref ttInfo; tempTTInfos)
                ttInfo = readVal!TempTTInfo(tzFile);

            //The array of time zone abbreviation characters.
            auto tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt);

            auto leapSeconds = new LeapSecond[](tzh_leapcnt);
            foreach(ref leapSecond; leapSeconds)
            {
                //The time_t when the leap second occurs.
                auto timeT = readVal!int(tzFile);

                //The total number of leap seconds to be applied after
                //the corresponding leap second.
                auto total = readVal!int(tzFile);

                leapSecond = LeapSecond(timeT, total);
            }

            //Indicate whether each corresponding DST transition were specified
            //in standard time or wall clock time.
            auto transitionIsStd = new bool[](tzh_ttisstdcnt);
            foreach(ref isStd; transitionIsStd)
                isStd = readVal!bool(tzFile);

            //Indicate whether each corresponding DST transition associated with
            //local time types are specified in UTC or local time.
            auto transitionInUTC = new bool[](tzh_ttisgmtcnt);
            foreach(ref inUTC; transitionInUTC)
                inUTC = readVal!bool(tzFile);

            _enforceValidTZFile(!tzFile.eof);

            //If version 2 or 3, the information is duplicated in 64-bit.
            if(tzFileVersion == '2' || tzFileVersion == '3')
            {
                _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif");

                immutable char tzFileVersion2 = readVal!(char)(tzFile);
                _enforceValidTZFile(tzFileVersion2 == '2' || tzFileVersion2 == '3');

                {
                    auto zeroBlock = readVal!(ubyte[])(tzFile, 15);
                    bool allZeroes = true;

                    foreach(val; zeroBlock)
                    {
                        if(val != 0)
                        {
                            allZeroes = false;
                            break;
                        }
                    }

                    _enforceValidTZFile(allZeroes);
                }


                //The number of UTC/local indicators stored in the file.
                tzh_ttisgmtcnt = readVal!int(tzFile);

                //The number of standard/wall indicators stored in the file.
                tzh_ttisstdcnt = readVal!int(tzFile);

                //The number of leap seconds for which data is stored in the file.
                tzh_leapcnt = readVal!int(tzFile);

                //The number of "transition times" for which data is stored in the file.
                tzh_timecnt = readVal!int(tzFile);

                //The number of "local time types" for which data is stored in the file (must not be zero).
                tzh_typecnt = readVal!int(tzFile);
                _enforceValidTZFile(tzh_typecnt != 0);

                //The number of characters of "timezone abbreviation strings" stored in the file.
                tzh_charcnt = readVal!int(tzFile);

                //time_ts where DST transitions occur.
                transitionTimeTs = new long[](tzh_timecnt);
                foreach(ref transition; transitionTimeTs)
                    transition = readVal!long(tzFile);

                //Indices into ttinfo structs indicating the changes
                //to be made at the corresponding DST transition.
                ttInfoIndices = new ubyte[](tzh_timecnt);
                foreach(ref ttInfoIndex; ttInfoIndices)
                    ttInfoIndex = readVal!ubyte(tzFile);

                //ttinfos which give info on DST transitions.
                tempTTInfos = new TempTTInfo[](tzh_typecnt);
                foreach(ref ttInfo; tempTTInfos)
                    ttInfo = readVal!TempTTInfo(tzFile);

                //The array of time zone abbreviation characters.
                tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt);

                leapSeconds = new LeapSecond[](tzh_leapcnt);
                foreach(ref leapSecond; leapSeconds)
                {
                    //The time_t when the leap second occurs.
                    auto timeT = readVal!long(tzFile);

                    //The total number of leap seconds to be applied after
                    //the corresponding leap second.
                    auto total = readVal!int(tzFile);

                    leapSecond = LeapSecond(timeT, total);
                }

                //Indicate whether each corresponding DST transition were specified
                //in standard time or wall clock time.
                transitionIsStd = new bool[](tzh_ttisstdcnt);
                foreach(ref isStd; transitionIsStd)
                    isStd = readVal!bool(tzFile);

                //Indicate whether each corresponding DST transition associated with
                //local time types are specified in UTC or local time.
                transitionInUTC = new bool[](tzh_ttisgmtcnt);
                foreach(ref inUTC; transitionInUTC)
                    inUTC = readVal!bool(tzFile);
            }

            _enforceValidTZFile(tzFile.readln().strip().empty);

            auto posixEnvStr = tzFile.readln().strip();

            version(Android)
            {
                // Android uses a single file for all timezone data, so the file
                // doesn't end here.
            }
            else
            {
                _enforceValidTZFile(tzFile.readln().strip().empty);
                _enforceValidTZFile(tzFile.eof);
            }


            auto transitionTypes = new TransitionType*[](tempTTInfos.length);

            foreach(i, ref ttype; transitionTypes)
            {
                bool isStd = false;

                if(i < transitionIsStd.length && !transitionIsStd.empty)
                    isStd = transitionIsStd[i];

                bool inUTC = false;

                if(i < transitionInUTC.length && !transitionInUTC.empty)
                    inUTC = transitionInUTC[i];

                ttype = new TransitionType(isStd, inUTC);
            }

            auto ttInfos = new immutable(TTInfo)*[](tempTTInfos.length);
            foreach(i, ref ttInfo; ttInfos)
            {
                auto tempTTInfo = tempTTInfos[i];

                if(gmtZone)
                    tempTTInfo.tt_gmtoff = -tempTTInfo.tt_gmtoff;

                auto abbrevChars = tzAbbrevChars[tempTTInfo.tt_abbrind .. $];
                string abbrev = abbrevChars[0 .. abbrevChars.countUntil('\0')].idup;

                ttInfo = new immutable(TTInfo)(tempTTInfos[i], abbrev);
            }

            auto tempTransitions = new TempTransition[](transitionTimeTs.length);
            foreach(i, ref tempTransition; tempTransitions)
            {
                immutable ttiIndex = ttInfoIndices[i];
                auto transitionTimeT = transitionTimeTs[i];
                auto ttype = transitionTypes[ttiIndex];
                auto ttInfo = ttInfos[ttiIndex];

                tempTransition = TempTransition(transitionTimeT, ttInfo, ttype);
            }

            if(tempTransitions.empty)
            {
                _enforceValidTZFile(ttInfos.length == 1 && transitionTypes.length == 1);
                tempTransitions ~= TempTransition(0, ttInfos[0], transitionTypes[0]);
            }

            sort!"a.timeT < b.timeT"(tempTransitions);
            sort!"a.timeT < b.timeT"(leapSeconds);

            auto transitions = new Transition[](tempTransitions.length);
            foreach(i, ref transition; transitions)
            {
                auto tempTransition = tempTransitions[i];
                auto transitionTimeT = tempTransition.timeT;
                auto ttInfo = tempTransition.ttInfo;
                auto ttype = tempTransition.ttype;

                _enforceValidTZFile(i == 0 || transitionTimeT > tempTransitions[i - 1].timeT);

                transition = Transition(transitionTimeT, ttInfo);
            }

            string stdName;
            string dstName;
            bool hasDST = false;

            foreach(transition; retro(transitions))
            {
                auto ttInfo = transition.ttInfo;

                if(ttInfo.isDST)
                {
                    if(dstName.empty)
                        dstName = ttInfo.abbrev;

                    hasDST = true;
                }
                else
                {
                    if(stdName.empty)
                        stdName = ttInfo.abbrev;
                }

                if(!stdName.empty && !dstName.empty)
                    break;
            }

            return new immutable PosixTimeZone(transitions.idup, leapSeconds.idup, name, stdName, dstName, hasDST);
        }
        catch(DateTimeException dte)
            throw dte;
        catch(Exception e)
            throw new DateTimeException("Not a valid TZ data file", __FILE__, __LINE__, e);
    }

    ///
    unittest
    {
        version(Posix)
        {
            auto tz = PosixTimeZone.getTimeZone("America/Los_Angeles");

            assert(tz.name == "America/Los_Angeles");
            assert(tz.stdName == "PST");
            assert(tz.dstName == "PDT");
        }
    }

    /++
        Returns a list of the names of the time zones installed on the system.

        Providing a sub-name narrows down the list of time zones (which
        can number in the thousands). For example,
        passing in "America" as the sub-name returns only the time zones which
        begin with "America".

        Params:
            subName       = The first part of the desired time zones.
            tzDatabaseDir = The directory where the TZ Database files are
                            located.

        Throws:
            $(D FileException) if it fails to read from disk.
      +/
    static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @trusted
    {
        import std.array : appender;
        import std.algorithm : sort;
        import std.format : format;

        version(Posix)
            subName = strip(subName);
        else version(Windows)
        {
            import std.array : replace;
            import std.path : dirSeparator;
            subName = replace(strip(subName), "/", dirSeparator);
        }

        enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir)));
        enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir)));

        auto timezones = appender!(string[])();

        version(Android)
        {
            import std.algorithm : copy, filter;
            tzdataIndex(tzDatabaseDir)
            .byKey
            .filter!(a => a.startsWith(subName))
            .copy(timezones);
        }
        else
        {
            foreach(DirEntry dentry; dirEntries(tzDatabaseDir, SpanMode.depth))
            {
                if(dentry.isFile)
                {
                    auto tzName = dentry.name[tzDatabaseDir.length .. $];

                    if(!tzName.extension().empty ||
                       !tzName.startsWith(subName) ||
                       tzName == "leapseconds" ||
                       tzName == "+VERSION")
                    {
                        continue;
                    }

                    timezones.put(tzName);
                }
            }
        }

        sort(timezones.data);

        return timezones.data;
    }

    version(Posix) unittest
    {
        static void testPTZSuccess(string tzName)
        {
            scope(failure) writefln("TZName which threw: %s", tzName);

            PosixTimeZone.getTimeZone(tzName);
        }

        static void testPTZFailure(string tzName)
        {
            scope(success) writefln("TZName which was supposed to throw: %s", tzName);

            PosixTimeZone.getTimeZone(tzName);
        }

        auto tzNames = getInstalledTZNames();

        foreach(tzName; tzNames)
            assertNotThrown!DateTimeException(testPTZSuccess(tzName));

        // No timezone directories on Android, just a single tzdata file
        version(Android) {} else
        foreach(DirEntry dentry; dirEntries(defaultTZDatabaseDir, SpanMode.depth))
        {
            if(dentry.isFile)
            {
                auto tzName = dentry.name[defaultTZDatabaseDir.length .. $];

                if(!canFind(tzNames, tzName))
                    assertThrown!DateTimeException(testPTZFailure(tzName));
            }
        }
    }


private:

    /+
        Holds information on when a time transition occures (usually a
        transition to or from DST) as well as a pointer to the $(D TTInfo) which
        holds information on the utc offset past the transition.
      +/
    struct Transition
    {
        this(long timeT, immutable (TTInfo)* ttInfo) @safe pure
        {
            this.timeT = timeT;
            this.ttInfo = ttInfo;
        }

        long    timeT;
        immutable (TTInfo)* ttInfo;
    }


    /+
        Holds information on when a leap second occurs.
      +/
    struct LeapSecond
    {
        this(long timeT, int total) @safe pure
        {
            this.timeT = timeT;
            this.total = total;
        }

        long timeT;
        int total;
    }

    /+
        Holds information on the utc offset after a transition as well as
        whether DST is in effect after that transition.
      +/
    struct TTInfo
    {
        this(in TempTTInfo tempTTInfo, string abbrev) @safe immutable pure
        {
            utcOffset = tempTTInfo.tt_gmtoff;
            isDST = tempTTInfo.tt_isdst;
            this.abbrev = abbrev;
        }

        immutable int    utcOffset;  /// Offset from UTC.
        immutable bool   isDST;      /// Whether DST is in effect.
        immutable string abbrev;     /// The current abbreviation for the time zone.
    }


    /+
        Struct used to hold information relating to $(D TTInfo) while organizing
        the time zone information prior to putting it in its final form.
      +/
    struct TempTTInfo
    {
        this(int gmtOff, bool isDST, ubyte abbrInd) @safe pure
        {
            tt_gmtoff = gmtOff;
            tt_isdst = isDST;
            tt_abbrind = abbrInd;
        }

        int   tt_gmtoff;
        bool  tt_isdst;
        ubyte tt_abbrind;
    }


    /+
        Struct used to hold information relating to $(D Transition) while
        organizing the time zone information prior to putting it in its final
        form.
      +/
    struct TempTransition
    {
        this(long timeT, immutable (TTInfo)* ttInfo, TransitionType* ttype) @safe pure
        {
            this.timeT = timeT;
            this.ttInfo = ttInfo;
            this.ttype = ttype;
        }

        long                timeT;
        immutable (TTInfo)* ttInfo;
        TransitionType*     ttype;
    }


    /+
        Struct used to hold information relating to $(D Transition) and
        $(D TTInfo) while organizing the time zone information prior to putting
        it in its final form.
      +/
    struct TransitionType
    {
        this(bool isStd, bool inUTC) @safe pure
        {
            this.isStd = isStd;
            this.inUTC = inUTC;
        }

        /// Whether the transition is in std time (as opposed to wall clock time).
        bool isStd;

        /// Whether the transition is in UTC (as opposed to local time).
        bool inUTC;
    }


    /+
        Reads an int from a TZ file.
      +/
    static T readVal(T)(ref File tzFile) @trusted
        if((isIntegral!T || isSomeChar!T) || is(Unqual!T == bool))
    {
        import std.bitmanip;
        T[1] buff;

        _enforceValidTZFile(!tzFile.eof);
        tzFile.rawRead(buff);

        return bigEndianToNative!T(cast(ubyte[T.sizeof])buff);
    }

    /+
        Reads an array of values from a TZ file.
      +/
    static T readVal(T)(ref File tzFile, size_t length) @trusted
        if(isArray!T)
    {
        auto buff = new T(length);

        _enforceValidTZFile(!tzFile.eof);
        tzFile.rawRead(buff);

        return buff;
    }


    /+
        Reads a $(D TempTTInfo) from a TZ file.
      +/
    static T readVal(T)(ref File tzFile) @safe
        if(is(T == TempTTInfo))
    {
        return TempTTInfo(readVal!int(tzFile),
                          readVal!bool(tzFile),
                          readVal!ubyte(tzFile));
    }


    /+
        Throws:
            $(LREF DateTimeException) if $(D result) is false.
      +/
    static void _enforceValidTZFile(bool result, size_t line = __LINE__) @safe pure
    {
        if(!result)
            throw new DateTimeException("Not a valid tzdata file.", __FILE__, line);
    }


    int calculateLeapSeconds(long stdTime) @safe const pure nothrow
    {
        if(_leapSeconds.empty)
            return 0;

        immutable unixTime = stdTimeToUnixTime(stdTime);

        if(_leapSeconds.front.timeT >= unixTime)
            return 0;

        immutable found = countUntil!"b < a.timeT"(_leapSeconds, unixTime);

        if(found == -1)
            return _leapSeconds.back.total;

        immutable leapSecond = found == 0 ? _leapSeconds[0] : _leapSeconds[found - 1];

        return leapSecond.total;
    }


    this(immutable Transition[] transitions,
         immutable LeapSecond[] leapSeconds,
         string name,
         string stdName,
         string dstName,
         bool hasDST) @safe immutable pure
    {
        if(dstName.empty && !stdName.empty)
            dstName = stdName;
        else if(stdName.empty && !dstName.empty)
            stdName = dstName;

        super(name, stdName, dstName);

        if(!transitions.empty)
        {
            foreach(i, transition; transitions[0 .. $-1])
                _enforceValidTZFile(transition.timeT < transitions[i + 1].timeT);
        }

        foreach(i, leapSecond; leapSeconds)
            _enforceValidTZFile(i == leapSeconds.length - 1 || leapSecond.timeT < leapSeconds[i + 1].timeT);

        _transitions = transitions;
        _leapSeconds = leapSeconds;
        _hasDST = hasDST;
    }

    // Android concatenates the usual timezone directories into a single file,
    // tzdata, along with an index to jump to each timezone's offset.  In older
    // versions of Android, the index was stored in a separate file, zoneinfo.idx,
    // whereas now it's stored at the beginning of tzdata.
    version(Android)
    {
        // Keep track of whether there's a separate index, zoneinfo.idx.  Only
        // check this after calling tzdataIndex, as it's initialized there.
        static shared bool separate_index;

        // Extracts the name of each time zone and the offset where its data is
        // located in the tzdata file from the index and caches it for later.
        static const(uint[string]) tzdataIndex(string tzDir)
        {
            import std.concurrency : initOnce;

            static __gshared uint[string] _tzIndex;

            // _tzIndex is initialized once and then shared across all threads.
            initOnce!_tzIndex(
            {
                import std.conv : to;
                import std.format : format;
                import std.path : asNormalizedPath, chainPath;

                enum indexEntrySize = 52;
                const combinedFile = asNormalizedPath(chainPath(tzDir, "tzdata")).to!string;
                const indexFile = asNormalizedPath(chainPath(tzDir, "zoneinfo.idx")).to!string;
                File tzFile;
                uint indexEntries, dataOffset;
                uint[string] initIndex;

                // Check for the combined file tzdata, which stores the index
                // and the time zone data together.
                if(combinedFile.exists() && combinedFile.isFile)
                {
                    tzFile = File(combinedFile);
                    _enforceValidTZFile(readVal!(char[])(tzFile, 6) == "tzdata");
                    auto tzDataVersion = readVal!(char[])(tzFile, 6);
                    _enforceValidTZFile(tzDataVersion[5] == '\0');

                    uint indexOffset = readVal!uint(tzFile);
                    dataOffset = readVal!uint(tzFile);
                    readVal!uint(tzFile);

                    indexEntries = (dataOffset - indexOffset)/indexEntrySize;
                    separate_index = false;
                }
                else if(indexFile.exists() && indexFile.isFile)
                {
                    tzFile = File(indexFile);
                    indexEntries = to!(uint)(tzFile.size/indexEntrySize);
                    separate_index = true;
                }
                else
                    throw new DateTimeException(format("Both timezone files %s and %s do not exist.",
                        combinedFile, indexFile));

                foreach(Unused; 0 .. indexEntries) {
                    string tzName = to!string(readVal!(char[])(tzFile, 40).ptr);
                    uint tzOffset = readVal!uint(tzFile);
                    readVal!(uint[])(tzFile, 2);
                    initIndex[tzName] = dataOffset + tzOffset;
                }
                initIndex.rehash;
                return initIndex;
            }());
            return _tzIndex;
        }
    }

    /// List of times when the utc offset changes.
    immutable Transition[] _transitions;

    /// List of leap second occurrences.
    immutable LeapSecond[] _leapSeconds;

    /// Whether DST is in effect for this time zone at any point in time.
    immutable bool _hasDST;
}



version(StdDdoc)
{
    /++
        $(BLUE This class is Windows-Only.)

        Represents a time zone from the Windows registry. Unfortunately, Windows
        does not use the TZ Database. To use the TZ Database, use
        $(LREF PosixTimeZone) (which reads its information from the TZ Database
        files on disk) on Windows by providing the TZ Database files and telling
        $(D PosixTimeZone.getTimeZone) where the directory holding them is.

        The TZ Database files and Windows' time zone information frequently
        do not match. Windows has many errors with regards to when DST switches
        occur (especially for historical dates). Also, the TZ Database files
        include far more time zones than Windows does. So, for accurate
        time zone information, use the TZ Database files with
        $(LREF PosixTimeZone) rather than $(D WindowsTimeZone). However, because
        $(D WindowsTimeZone) uses Windows system calls to deal with the time,
        it's far more likely to match the behavior of other Windows programs.
        Be aware of the differences when selecting a method.

        $(D WindowsTimeZone) does not exist on Posix systems.

        To get a $(D WindowsTimeZone), either call
        $(D WindowsTimeZone.getTimeZone) or call $(D TimeZone.getTimeZone)
        (which will give a $(LREF PosixTimeZone) on Posix systems and a
         $(D WindowsTimeZone) on Windows systems).

        See_Also:
            $(WEB www.iana.org/time-zones, Home of the TZ Database files)
      +/
    final class WindowsTimeZone : TimeZone
    {
    public:

        /++
            Whether this time zone has Daylight Savings Time at any point in
            time. Note that for some time zone types it may not have DST for
            current dates but will still return true for $(D hasDST) because the
            time zone did at some point have DST.
          +/
        @property override bool hasDST() @safe const nothrow;


        /++
            Takes the number of hnsecs (100 ns) since midnight, January 1st,
            1 A.D. in UTC time (i.e. std time) and returns whether DST is in
            effect in this time zone at the given point in time.

            Params:
                stdTime = The UTC time that needs to be checked for DST in this
                          time zone.
          +/
        override bool dstInEffect(long stdTime) @safe const nothrow;


        /++
            Takes the number of hnsecs (100 ns) since midnight, January 1st,
            1 A.D. in UTC time (i.e. std time) and converts it to this time
                zone's time.

            Params:
                stdTime = The UTC time that needs to be adjusted to this time
                          zone's time.
          +/
        override long utcToTZ(long stdTime) @safe const nothrow;


        /++
            Takes the number of hnsecs (100 ns) since midnight, January 1st,
            1 A.D. in this time zone's time and converts it to UTC (i.e. std
            time).

            Params:
                adjTime = The time in this time zone that needs to be adjusted
                          to UTC time.
          +/
        override long tzToUTC(long adjTime) @safe const nothrow;


        /++
            Returns a $(LREF2 .TimeZone, TimeZone) with the given name per the Windows time
            zone names. The time zone information is fetched from the Windows
            registry.

            See_Also:
                $(WEB en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
                  Database)<br>
                $(WEB en.wikipedia.org/wiki/List_of_tz_database_time_zones, List
                  of Time Zones)

            Params:
                name = The TZ Database name of the desired time zone.

            Throws:
                $(LREF DateTimeException) if the given time zone could not be
                found.

            Example:
    --------------------
    auto tz = TimeZone.getTimeZone("America/Los_Angeles");
    --------------------
          +/
        static immutable(WindowsTimeZone) getTimeZone(string name) @safe;


        /++
            Returns a list of the names of the time zones installed on the
            system. The list returned by WindowsTimeZone contains the Windows
            TZ names, not the TZ Database names. However,
            $(D TimeZone.getinstalledTZNames) will return the TZ Database names
            which are equivalent to the Windows TZ names.
          +/
        static string[] getInstalledTZNames() @safe;

    private:

        version(Windows) {}
        else
            alias TIME_ZONE_INFORMATION = void*;

        static bool _dstInEffect(const TIME_ZONE_INFORMATION* tzInfo, long stdTime) nothrow;
        static long _utcToTZ(const TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) nothrow;
        static long _tzToUTC(const TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) nothrow;

        this() immutable pure
        {
            super("", "", "");
        }
    }

}
else version(Windows)
{
    final class WindowsTimeZone : TimeZone
    {
        import std.format : format;
        import std.conv : to;
        import std.algorithm : sort;
        import std.array : appender;

    public:

        @property override bool hasDST() @safe const nothrow
        {
            return _tzInfo.DaylightDate.wMonth != 0;
        }


        override bool dstInEffect(long stdTime) @safe const nothrow
        {
            return _dstInEffect(&_tzInfo, stdTime);
        }


        override long utcToTZ(long stdTime) @safe const nothrow
        {
            return _utcToTZ(&_tzInfo, stdTime, hasDST);
        }


        override long tzToUTC(long adjTime) @safe const nothrow
        {
            return _tzToUTC(&_tzInfo, adjTime, hasDST);
        }


        static immutable(WindowsTimeZone) getTimeZone(string name) @trusted
        {
            import std.utf : toUTF16;

            scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`);

            foreach (tzKeyName; baseKey.keyNames)
            {
                if (tzKeyName != name)
                    continue;

                scope tzKey = baseKey.getKey(tzKeyName);

                scope stdVal = tzKey.getValue("Std");
                auto stdName = stdVal.value_SZ;

                scope dstVal = tzKey.getValue("Dlt");
                auto dstName = dstVal.value_SZ;

                scope tziVal = tzKey.getValue("TZI");
                auto binVal = tziVal.value_BINARY;
                assert(binVal.length == REG_TZI_FORMAT.sizeof);
                auto tziFmt = cast(REG_TZI_FORMAT*)binVal.ptr;

                TIME_ZONE_INFORMATION tzInfo;

                auto wstdName = toUTF16(stdName);
                auto wdstName = toUTF16(dstName);
                auto wstdNameLen = wstdName.length > 32 ? 32 : wstdName.length;
                auto wdstNameLen = wdstName.length > 32 ? 32 : wdstName.length;

                tzInfo.Bias = tziFmt.Bias;
                tzInfo.StandardName[0 .. wstdNameLen] = wstdName[0 .. wstdNameLen];
                tzInfo.StandardName[wstdNameLen .. $] = '\0';
                tzInfo.StandardDate = tziFmt.StandardDate;
                tzInfo.StandardBias = tziFmt.StandardBias;
                tzInfo.DaylightName[0 .. wdstNameLen] = wdstName[0 .. wdstNameLen];
                tzInfo.DaylightName[wdstNameLen .. $] = '\0';
                tzInfo.DaylightDate = tziFmt.DaylightDate;
                tzInfo.DaylightBias = tziFmt.DaylightBias;

                return new immutable WindowsTimeZone(name, tzInfo);
            }
            throw new DateTimeException(format("Failed to find time zone: %s", name));
        }

        static string[] getInstalledTZNames() @trusted
        {
            auto timezones = appender!(string[])();

            scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`);

            foreach (tzKeyName; baseKey.keyNames)
            {
                timezones.put(tzKeyName);
            }
            sort(timezones.data);

            return timezones.data;
        }

        unittest
        {
            static void testWTZSuccess(string tzName)
            {
                scope(failure) writefln("TZName which threw: %s", tzName);

                WindowsTimeZone.getTimeZone(tzName);
            }

            auto tzNames = getInstalledTZNames();

            foreach(tzName; tzNames)
                assertNotThrown!DateTimeException(testWTZSuccess(tzName));
        }


    private:

        static bool _dstInEffect(const TIME_ZONE_INFORMATION* tzInfo, long stdTime) @trusted nothrow
        {
            try
            {
                if(tzInfo.DaylightDate.wMonth == 0)
                    return false;

                auto utcDateTime = cast(DateTime)SysTime(stdTime, UTC());

                //The limits of what SystemTimeToTzSpecificLocalTime will accept.
                if(utcDateTime.year < 1601)
                {
                    if(utcDateTime.month == Month.feb && utcDateTime.day == 29)
                        utcDateTime.day = 28;

                    utcDateTime.year = 1601;
                }
                else if(utcDateTime.year > 30_827)
                {
                    if(utcDateTime.month == Month.feb && utcDateTime.day == 29)
                        utcDateTime.day = 28;

                    utcDateTime.year = 30_827;
                }

                //SystemTimeToTzSpecificLocalTime doesn't act correctly at the
                //beginning or end of the year (bleh). Unless some bizarre time
                //zone changes DST on January 1st or December 31st, this should
                //fix the problem.
                if(utcDateTime.month == Month.jan)
                {
                    if(utcDateTime.day == 1)
                        utcDateTime.day = 2;
                }
                else if(utcDateTime.month == Month.dec && utcDateTime.day == 31)
                    utcDateTime.day = 30;

                SYSTEMTIME utcTime = void;
                SYSTEMTIME otherTime = void;

                utcTime.wYear = utcDateTime.year;
                utcTime.wMonth = utcDateTime.month;
                utcTime.wDay = utcDateTime.day;
                utcTime.wHour = utcDateTime.hour;
                utcTime.wMinute = utcDateTime.minute;
                utcTime.wSecond = utcDateTime.second;
                utcTime.wMilliseconds = 0;

                immutable result = SystemTimeToTzSpecificLocalTime(cast(TIME_ZONE_INFORMATION*)tzInfo,
                                                                   &utcTime,
                                                                   &otherTime);
                assert(result);

                immutable otherDateTime = DateTime(otherTime.wYear,
                                                   otherTime.wMonth,
                                                   otherTime.wDay,
                                                   otherTime.wHour,
                                                   otherTime.wMinute,
                                                   otherTime.wSecond);
                immutable diff = utcDateTime - otherDateTime;
                immutable minutes = diff.total!"minutes" - tzInfo.Bias;

                if(minutes == tzInfo.DaylightBias)
                    return true;

                assert(minutes == tzInfo.StandardBias);

                return false;
            }
            catch(Exception e)
                assert(0, "DateTime's constructor threw.");
        }

        unittest
        {
            TIME_ZONE_INFORMATION tzInfo;
            GetTimeZoneInformation(&tzInfo);

            foreach(year; [1600, 1601, 30_827, 30_828])
                WindowsTimeZone._dstInEffect(&tzInfo, SysTime(DateTime(year, 1, 1)).stdTime);
        }


        static long _utcToTZ(const TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) @safe nothrow
        {
            if(hasDST && WindowsTimeZone._dstInEffect(tzInfo, stdTime))
                return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias);

            return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias);
        }


        static long _tzToUTC(const TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) @trusted nothrow
        {
            if(hasDST)
            {
                try
                {
                    bool dstInEffectForLocalDateTime(DateTime localDateTime)
                    {
                        //The limits of what SystemTimeToTzSpecificLocalTime will accept.
                        if(localDateTime.year < 1601)
                        {
                            if(localDateTime.month == Month.feb && localDateTime.day == 29)
                                localDateTime.day = 28;

                            localDateTime.year = 1601;
                        }
                        else if(localDateTime.year > 30_827)
                        {
                            if(localDateTime.month == Month.feb && localDateTime.day == 29)
                                localDateTime.day = 28;

                            localDateTime.year = 30_827;
                        }

                        //SystemTimeToTzSpecificLocalTime doesn't act correctly at the
                        //beginning or end of the year (bleh). Unless some bizarre time
                        //zone changes DST on January 1st or December 31st, this should
                        //fix the problem.
                        if(localDateTime.month == Month.jan)
                        {
                            if(localDateTime.day == 1)
                                localDateTime.day = 2;
                        }
                        else if(localDateTime.month == Month.dec && localDateTime.day == 31)
                            localDateTime.day = 30;

                        SYSTEMTIME utcTime = void;
                        SYSTEMTIME localTime = void;

                        localTime.wYear = localDateTime.year;
                        localTime.wMonth = localDateTime.month;
                        localTime.wDay = localDateTime.day;
                        localTime.wHour = localDateTime.hour;
                        localTime.wMinute = localDateTime.minute;
                        localTime.wSecond = localDateTime.second;
                        localTime.wMilliseconds = 0;

                        immutable result = TzSpecificLocalTimeToSystemTime(cast(TIME_ZONE_INFORMATION*)tzInfo,
                                                                           &localTime,
                                                                           &utcTime);
                        assert(result);

                        immutable utcDateTime = DateTime(utcTime.wYear,
                                                         utcTime.wMonth,
                                                         utcTime.wDay,
                                                         utcTime.wHour,
                                                         utcTime.wMinute,
                                                         utcTime.wSecond);

                        immutable diff = localDateTime - utcDateTime;
                        immutable minutes = -tzInfo.Bias - diff.total!"minutes";

                        if(minutes == tzInfo.DaylightBias)
                            return true;

                        assert(minutes == tzInfo.StandardBias);

                        return false;
                    }

                    auto localDateTime = cast(DateTime)SysTime(adjTime, UTC());
                    auto localDateTimeBefore = localDateTime - dur!"hours"(1);
                    auto localDateTimeAfter = localDateTime + dur!"hours"(1);

                    auto dstInEffectNow = dstInEffectForLocalDateTime(localDateTime);
                    auto dstInEffectBefore = dstInEffectForLocalDateTime(localDateTimeBefore);
                    auto dstInEffectAfter = dstInEffectForLocalDateTime(localDateTimeAfter);

                    bool isDST;

                    if(dstInEffectBefore && dstInEffectNow && dstInEffectAfter)
                        isDST = true;
                    else if(!dstInEffectBefore && !dstInEffectNow && !dstInEffectAfter)
                        isDST = false;
                    else if(!dstInEffectBefore && dstInEffectAfter)
                        isDST = false;
                    else if(dstInEffectBefore && !dstInEffectAfter)
                        isDST = dstInEffectNow;
                    else
                        assert(0, "Bad Logic.");

                    if(isDST)
                        return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias);
                }
                catch(Exception e)
                    assert(0, "SysTime's constructor threw.");
            }

            return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias);
        }


        this(string name, TIME_ZONE_INFORMATION tzInfo) @safe immutable pure
        {
            super(name, to!string(tzInfo.StandardName.ptr), to!string(tzInfo.DaylightName.ptr));
            _tzInfo = tzInfo;
        }


        TIME_ZONE_INFORMATION _tzInfo;
    }
}


version(StdDdoc)
{
    /++
        $(BLUE This function is Posix-Only.)

        Sets the local time zone on Posix systems with the TZ
        Database name by setting the TZ environment variable.

        Unfortunately, there is no way to do it on Windows using the TZ
        Database name, so this function only exists on Posix systems.
      +/
    void setTZEnvVar(string tzDatabaseName) @safe nothrow;


    /++
        $(BLUE This function is Posix-Only.)

        Clears the TZ environment variable.
      +/
    void clearTZEnvVar() @safe nothrow;
}
else version(Posix)
{
    void setTZEnvVar(string tzDatabaseName) @trusted nothrow
    {
        import std.internal.cstring : tempCString;
        import std.path : asNormalizedPath, chainPath;
        import core.sys.posix.stdlib : setenv;
        import core.sys.posix.time : tzset;

        version(Android)
            auto value = asNormalizedPath(tzDatabaseName);
        else
            auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName));
        setenv("TZ", value.tempCString(), 1);
        tzset();
    }


    void clearTZEnvVar() @trusted nothrow
    {
        import core.sys.posix.stdlib : unsetenv;
        import core.sys.posix.time : tzset;

        unsetenv("TZ");
        tzset();
    }
}


/++
    Provides the conversions between the IANA time zone database time zone names
    (which POSIX systems use) and the time zone names that Windows uses.

    Windows uses a different set of time zone names than the IANA time zone
    database does, and how they correspond to one another changes over time
    (particularly when Microsoft updates Windows).
    $(WEB http://unicode.org/cldr/data/common/supplemental/windowsZones.xml, windowsZones.xml)
    provides the current conversions (which may or may not match up with what's
    on a particular Windows box depending on how up-to-date it is), and
    parseTZConversions reads in those conversions from windowsZones.xml so that
    a D program can use those conversions.

    However, it should be noted that the time zone information on Windows is
    frequently less accurate than that in the IANA time zone database, and if
    someone really wants accurate time zone information, they should use the
    IANA time zone database files with $(LREF PosixTimeZone) on Windows rather
    than $(LREF WindowsTimeZone), whereas $(LREF WindowsTimeZone) makes more
    sense when trying to match what Windows will think the time is in a specific
    time zone.

    Also, the IANA time zone database has a lot more time zones than Windows
    does.

    Params:
        windowsZonesXMLFileText The text from
        $(WEB http://unicode.org/cldr/data/common/supplemental/windowsZones.xml, windowsZones.xml)

    Throws:
        Exception if there is an error while parsing the given XML.

--------------------
    // Parse the conversions from a local file.
    auto text = std.file.readText("path/to/windowsZones.xml");
    auto conversions = parseTZConversions(text);

    // Alternatively, grab the XML file from the web at runtime
    // and parse it so that it's guaranteed to be up-to-date, though
    // that has the downside that the code needs to worry about the
    // site being down or unicode.org changing the URL.
    auto url = "http://unicode.org/cldr/data/common/supplemental/windowsZones.xml";
    auto conversions2 = parseTZConversions(std.net.curl.get(url));
--------------------
  +/
struct TZConversions
{
    /++
        The key is the Windows time zone name, and the value is a list of
        IANA TZ database names which are close (currently only ever one, but
        it allows for multiple in case it's ever necessary).
      +/
    string[][string] toWindows;

    /++
        The key is the IANA time zone database name, and the value is a list of
        Windows time zone names which are close (usually only one, but it could
        be multiple).
      +/
    string[][string] fromWindows;
}

/++ ditto +/
TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure
{
    // This is a bit hacky, since it doesn't properly read XML, but it avoids
    // needing to pull in std.xml (which we're theoretically replacing at some
    // point anyway.
    import std.algorithm : find, sort, uniq;
    import std.array : array, split;
    import std.string : lineSplitter;

    string[][string] win2Nix;
    string[][string] nix2Win;

    immutable f1 = `<mapZone other="`;
    immutable f2 = `type="`;

    foreach(line; windowsZonesXMLText.lineSplitter())
    {
        // Sample line:
        // <mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>

        line = line.find(f1);
        if(line.empty)
            continue;
        line = line[f1.length .. $];
        auto next = line.find('"');
        enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
        auto win = line[0 .. $ - next.length];
        line = next.find(f2);
        enforce(!line.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
        line = line[f2.length .. $];
        next = line.find('"');
        enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
        auto nixes = line[0 .. $ - next.length].split();

        if(auto n = win in win2Nix)
            *n ~= nixes;
        else
            win2Nix[win] = nixes;

        foreach(nix; nixes)
        {
            if(auto w = nix in nix2Win)
                *w ~= win;
            else
                nix2Win[nix] = [win];
        }
    }

    foreach(key, ref value; nix2Win)
        value = value.sort().uniq().array();
    foreach(key, ref value; win2Nix)
        value = value.sort().uniq().array();

    return TZConversions(nix2Win, win2Nix);
}

unittest
{
    import std.algorithm;

    // Reduced text from http://unicode.org/cldr/data/common/supplemental/windowsZones.xml
    auto sampleFileText =
`<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<!--
Copyright © 1991-2013 Unicode, Inc.
CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
For terms of use, see http://www.unicode.org/copyright.html
-->

<supplementalData>
    <version number="$Revision$"/>

    <windowsZones>
        <mapTimezones otherVersion="7df0005" typeVersion="2015g">

            <!-- (UTC-12:00) International Date Line West -->
            <mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
            <mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>

            <!-- (UTC-11:00) Coordinated Universal Time-11 -->
            <mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
            <mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
            <mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
            <mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
            <mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>

            <!-- (UTC-10:00) Hawaii -->
            <mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
            <mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
            <mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
            <mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/>
            <mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
            <mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>

            <!-- (UTC-09:00) Alaska -->
            <mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
            <mapZone other="Alaskan Standard Time" territory="US" type="America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"/>
        </mapTimezones>
    </windowsZones>
</supplementalData>`;

    auto tzConversions = parseTZConversions(sampleFileText);
    assert(tzConversions.toWindows.length == 15);
    assert(tzConversions.toWindows["America/Anchorage"] == ["Alaskan Standard Time"]);
    assert(tzConversions.toWindows["America/Juneau"] == ["Alaskan Standard Time"]);
    assert(tzConversions.toWindows["America/Nome"] == ["Alaskan Standard Time"]);
    assert(tzConversions.toWindows["America/Sitka"] == ["Alaskan Standard Time"]);
    assert(tzConversions.toWindows["America/Yakutat"] == ["Alaskan Standard Time"]);
    assert(tzConversions.toWindows["Etc/GMT+10"] == ["Hawaiian Standard Time"]);
    assert(tzConversions.toWindows["Etc/GMT+11"] == ["UTC-11"]);
    assert(tzConversions.toWindows["Etc/GMT+12"] == ["Dateline Standard Time"]);
    assert(tzConversions.toWindows["Pacific/Honolulu"] == ["Hawaiian Standard Time"]);
    assert(tzConversions.toWindows["Pacific/Johnston"] == ["Hawaiian Standard Time"]);
    assert(tzConversions.toWindows["Pacific/Midway"] == ["UTC-11"]);
    assert(tzConversions.toWindows["Pacific/Niue"] == ["UTC-11"]);
    assert(tzConversions.toWindows["Pacific/Pago_Pago"] == ["UTC-11"]);
    assert(tzConversions.toWindows["Pacific/Rarotonga"] == ["Hawaiian Standard Time"]);
    assert(tzConversions.toWindows["Pacific/Tahiti"] == ["Hawaiian Standard Time"]);

    assert(tzConversions.fromWindows.length == 4);
    assert(tzConversions.fromWindows["Alaskan Standard Time"] ==
           ["America/Anchorage", "America/Juneau", "America/Nome", "America/Sitka", "America/Yakutat"]);
    assert(tzConversions.fromWindows["Dateline Standard Time"] == ["Etc/GMT+12"]);
    assert(tzConversions.fromWindows["Hawaiian Standard Time"] ==
           ["Etc/GMT+10", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Rarotonga", "Pacific/Tahiti"]);
    assert(tzConversions.fromWindows["UTC-11"] ==
           ["Etc/GMT+11", "Pacific/Midway", "Pacific/Niue", "Pacific/Pago_Pago"]);

    foreach(key, value; tzConversions.fromWindows)
    {
        assert(value.isSorted, key);
        assert(equal(value.uniq(), value), key);
    }
}


/++
    $(RED Please use $(LREF parseTZConversions) instead. This function will be
          deprecated in 2.072, because Microsoft changes their time zones too
          often for us to compile the conversions into Phobos and have them be
          properly up-to-date.)

    Converts the given TZ Database name to the corresponding Windows time zone
    name.

    Note that in a few cases, a TZ Dabatase name corresponds to two different
    Windows time zone names. So, while in most cases converting from one to the
    other and back again will result in the same time zone name started
    with, in a few case, it'll get a different name.

    Also, there are far more TZ Database names than Windows time zones, so some
    of the more exotic TZ Database names don't have corresponding Windows time
    zone names.

    Returns null if the given time zone name cannot be converted.

    See_Also:
        $(WEB unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html,
              Windows <-> TZ Database Name Conversion Table)

    Params:
        tzName = The TZ Database name to convert.
  +/
string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc
{
    switch(tzName)
    {
        case "Africa/Abidjan": return "Greenwich Standard Time";
        case "Africa/Accra": return "Greenwich Standard Time";
        case "Africa/Addis_Ababa": return "E. Africa Standard Time";
        case "Africa/Algiers": return "W. Central Africa Standard Time";
        case "Africa/Asmera": return "E. Africa Standard Time";
        case "Africa/Bamako": return "Greenwich Standard Time";
        case "Africa/Bangui": return "W. Central Africa Standard Time";
        case "Africa/Banjul": return "Greenwich Standard Time";
        case "Africa/Bissau": return "Greenwich Standard Time";
        case "Africa/Blantyre": return "South Africa Standard Time";
        case "Africa/Brazzaville": return "W. Central Africa Standard Time";
        case "Africa/Bujumbura": return "South Africa Standard Time";
        case "Africa/Cairo": return "Egypt Standard Time";
        case "Africa/Casablanca": return "Morocco Standard Time";
        case "Africa/Ceuta": return "Romance Standard Time";
        case "Africa/Conakry": return "Greenwich Standard Time";
        case "Africa/Dakar": return "Greenwich Standard Time";
        case "Africa/Dar_es_Salaam": return "E. Africa Standard Time";
        case "Africa/Djibouti": return "E. Africa Standard Time";
        case "Africa/Douala": return "W. Central Africa Standard Time";
        case "Africa/El_Aaiun": return "Morocco Standard Time";
        case "Africa/Freetown": return "Greenwich Standard Time";
        case "Africa/Gaborone": return "South Africa Standard Time";
        case "Africa/Harare": return "South Africa Standard Time";
        case "Africa/Johannesburg": return "South Africa Standard Time";
        case "Africa/Juba": return "E. Africa Standard Time";
        case "Africa/Kampala": return "E. Africa Standard Time";
        case "Africa/Khartoum": return "E. Africa Standard Time";
        case "Africa/Kigali": return "South Africa Standard Time";
        case "Africa/Kinshasa": return "W. Central Africa Standard Time";
        case "Africa/Lagos": return "W. Central Africa Standard Time";
        case "Africa/Libreville": return "W. Central Africa Standard Time";
        case "Africa/Lome": return "Greenwich Standard Time";
        case "Africa/Luanda": return "W. Central Africa Standard Time";
        case "Africa/Lubumbashi": return "South Africa Standard Time";
        case "Africa/Lusaka": return "South Africa Standard Time";
        case "Africa/Malabo": return "W. Central Africa Standard Time";
        case "Africa/Maputo": return "South Africa Standard Time";
        case "Africa/Maseru": return "South Africa Standard Time";
        case "Africa/Mbabane": return "South Africa Standard Time";
        case "Africa/Mogadishu": return "E. Africa Standard Time";
        case "Africa/Monrovia": return "Greenwich Standard Time";
        case "Africa/Nairobi": return "E. Africa Standard Time";
        case "Africa/Ndjamena": return "W. Central Africa Standard Time";
        case "Africa/Niamey": return "W. Central Africa Standard Time";
        case "Africa/Nouakchott": return "Greenwich Standard Time";
        case "Africa/Ouagadougou": return "Greenwich Standard Time";
        case "Africa/Porto-Novo": return "W. Central Africa Standard Time";
        case "Africa/Sao_Tome": return "Greenwich Standard Time";
        case "Africa/Tripoli": return "Libya Standard Time";
        case "Africa/Tunis": return "W. Central Africa Standard Time";
        case "Africa/Windhoek": return "Namibia Standard Time";
        case "America/Anchorage": return "Alaskan Standard Time";
        case "America/Anguilla": return "SA Western Standard Time";
        case "America/Antigua": return "SA Western Standard Time";
        case "America/Araguaina": return "SA Eastern Standard Time";
        case "America/Argentina/La_Rioja": return "Argentina Standard Time";
        case "America/Argentina/Rio_Gallegos": return "Argentina Standard Time";
        case "America/Argentina/Salta": return "Argentina Standard Time";
        case "America/Argentina/San_Juan": return "Argentina Standard Time";
        case "America/Argentina/San_Luis": return "Argentina Standard Time";
        case "America/Argentina/Tucuman": return "Argentina Standard Time";
        case "America/Argentina/Ushuaia": return "Argentina Standard Time";
        case "America/Aruba": return "SA Western Standard Time";
        case "America/Asuncion": return "Paraguay Standard Time";
        case "America/Bahia": return "Bahia Standard Time";
        case "America/Bahia_Banderas": return "Central Standard Time (Mexico)";
        case "America/Barbados": return "SA Western Standard Time";
        case "America/Belem": return "SA Eastern Standard Time";
        case "America/Belize": return "Central America Standard Time";
        case "America/Blanc-Sablon": return "SA Western Standard Time";
        case "America/Boa_Vista": return "SA Western Standard Time";
        case "America/Bogota": return "SA Pacific Standard Time";
        case "America/Boise": return "Mountain Standard Time";
        case "America/Buenos_Aires": return "Argentina Standard Time";
        case "America/Cambridge_Bay": return "Mountain Standard Time";
        case "America/Campo_Grande": return "Central Brazilian Standard Time";
        case "America/Cancun": return "Eastern Standard Time (Mexico)";
        case "America/Caracas": return "Venezuela Standard Time";
        case "America/Catamarca": return "Argentina Standard Time";
        case "America/Cayenne": return "SA Eastern Standard Time";
        case "America/Cayman": return "SA Pacific Standard Time";
        case "America/Chicago": return "Central Standard Time";
        case "America/Chihuahua": return "Mountain Standard Time (Mexico)";
        case "America/Coral_Harbour": return "SA Pacific Standard Time";
        case "America/Cordoba": return "Argentina Standard Time";
        case "America/Costa_Rica": return "Central America Standard Time";
        case "America/Creston": return "US Mountain Standard Time";
        case "America/Cuiaba": return "Central Brazilian Standard Time";
        case "America/Curacao": return "SA Western Standard Time";
        case "America/Danmarkshavn": return "UTC";
        case "America/Dawson": return "Pacific Standard Time";
        case "America/Dawson_Creek": return "US Mountain Standard Time";
        case "America/Denver": return "Mountain Standard Time";
        case "America/Detroit": return "Eastern Standard Time";
        case "America/Dominica": return "SA Western Standard Time";
        case "America/Edmonton": return "Mountain Standard Time";
        case "America/Eirunepe": return "SA Pacific Standard Time";
        case "America/El_Salvador": return "Central America Standard Time";
        case "America/Fortaleza": return "SA Eastern Standard Time";
        case "America/Glace_Bay": return "Atlantic Standard Time";
        case "America/Godthab": return "Greenland Standard Time";
        case "America/Goose_Bay": return "Atlantic Standard Time";
        case "America/Grand_Turk": return "SA Western Standard Time";
        case "America/Grenada": return "SA Western Standard Time";
        case "America/Guadeloupe": return "SA Western Standard Time";
        case "America/Guatemala": return "Central America Standard Time";
        case "America/Guayaquil": return "SA Pacific Standard Time";
        case "America/Guyana": return "SA Western Standard Time";
        case "America/Halifax": return "Atlantic Standard Time";
        case "America/Havana": return "Eastern Standard Time";
        case "America/Hermosillo": return "US Mountain Standard Time";
        case "America/Indiana/Knox": return "Central Standard Time";
        case "America/Indiana/Marengo": return "US Eastern Standard Time";
        case "America/Indiana/Petersburg": return "Eastern Standard Time";
        case "America/Indiana/Tell_City": return "Central Standard Time";
        case "America/Indiana/Vevay": return "US Eastern Standard Time";
        case "America/Indiana/Vincennes": return "Eastern Standard Time";
        case "America/Indiana/Winamac": return "Eastern Standard Time";
        case "America/Indianapolis": return "US Eastern Standard Time";
        case "America/Inuvik": return "Mountain Standard Time";
        case "America/Iqaluit": return "Eastern Standard Time";
        case "America/Jamaica": return "SA Pacific Standard Time";
        case "America/Jujuy": return "Argentina Standard Time";
        case "America/Juneau": return "Alaskan Standard Time";
        case "America/Kentucky/Monticello": return "Eastern Standard Time";
        case "America/Kralendijk": return "SA Western Standard Time";
        case "America/La_Paz": return "SA Western Standard Time";
        case "America/Lima": return "SA Pacific Standard Time";
        case "America/Los_Angeles": return "Pacific Standard Time";
        case "America/Louisville": return "Eastern Standard Time";
        case "America/Lower_Princes": return "SA Western Standard Time";
        case "America/Maceio": return "SA Eastern Standard Time";
        case "America/Managua": return "Central America Standard Time";
        case "America/Manaus": return "SA Western Standard Time";
        case "America/Marigot": return "SA Western Standard Time";
        case "America/Martinique": return "SA Western Standard Time";
        case "America/Matamoros": return "Central Standard Time";
        case "America/Mazatlan": return "Mountain Standard Time (Mexico)";
        case "America/Mendoza": return "Argentina Standard Time";
        case "America/Menominee": return "Central Standard Time";
        case "America/Merida": return "Central Standard Time (Mexico)";
        case "America/Mexico_City": return "Central Standard Time (Mexico)";
        case "America/Moncton": return "Atlantic Standard Time";
        case "America/Monterrey": return "Central Standard Time (Mexico)";
        case "America/Montevideo": return "Montevideo Standard Time";
        case "America/Montreal": return "Eastern Standard Time";
        case "America/Montserrat": return "SA Western Standard Time";
        case "America/Nassau": return "Eastern Standard Time";
        case "America/New_York": return "Eastern Standard Time";
        case "America/Nipigon": return "Eastern Standard Time";
        case "America/Nome": return "Alaskan Standard Time";
        case "America/Noronha": return "UTC-02";
        case "America/North_Dakota/Beulah": return "Central Standard Time";
        case "America/North_Dakota/Center": return "Central Standard Time";
        case "America/North_Dakota/New_Salem": return "Central Standard Time";
        case "America/Ojinaga": return "Mountain Standard Time";
        case "America/Panama": return "SA Pacific Standard Time";
        case "America/Pangnirtung": return "Eastern Standard Time";
        case "America/Paramaribo": return "SA Eastern Standard Time";
        case "America/Phoenix": return "US Mountain Standard Time";
        case "America/Port-au-Prince": return "Haiti Standard Time";
        case "America/Port_of_Spain": return "SA Western Standard Time";
        case "America/Porto_Velho": return "SA Western Standard Time";
        case "America/Puerto_Rico": return "SA Western Standard Time";
        case "America/Rainy_River": return "Central Standard Time";
        case "America/Rankin_Inlet": return "Central Standard Time";
        case "America/Recife": return "SA Eastern Standard Time";
        case "America/Regina": return "Canada Central Standard Time";
        case "America/Resolute": return "Central Standard Time";
        case "America/Rio_Branco": return "SA Pacific Standard Time";
        case "America/Santa_Isabel": return "Pacific Standard Time (Mexico)";
        case "America/Santarem": return "SA Eastern Standard Time";
        case "America/Santiago": return "Pacific SA Standard Time";
        case "America/Santo_Domingo": return "SA Western Standard Time";
        case "America/Sao_Paulo": return "E. South America Standard Time";
        case "America/Scoresbysund": return "Azores Standard Time";
        case "America/Sitka": return "Alaskan Standard Time";
        case "America/St_Barthelemy": return "SA Western Standard Time";
        case "America/St_Johns": return "Newfoundland Standard Time";
        case "America/St_Kitts": return "SA Western Standard Time";
        case "America/St_Lucia": return "SA Western Standard Time";
        case "America/St_Thomas": return "SA Western Standard Time";
        case "America/St_Vincent": return "SA Western Standard Time";
        case "America/Swift_Current": return "Canada Central Standard Time";
        case "America/Tegucigalpa": return "Central America Standard Time";
        case "America/Thule": return "Atlantic Standard Time";
        case "America/Thunder_Bay": return "Eastern Standard Time";
        case "America/Tijuana": return "Pacific Standard Time";
        case "America/Toronto": return "Eastern Standard Time";
        case "America/Tortola": return "SA Western Standard Time";
        case "America/Vancouver": return "Pacific Standard Time";
        case "America/Whitehorse": return "Pacific Standard Time";
        case "America/Winnipeg": return "Central Standard Time";
        case "America/Yakutat": return "Alaskan Standard Time";
        case "America/Yellowknife": return "Mountain Standard Time";
        case "Antarctica/Casey": return "W. Australia Standard Time";
        case "Antarctica/Davis": return "SE Asia Standard Time";
        case "Antarctica/DumontDUrville": return "West Pacific Standard Time";
        case "Antarctica/Macquarie": return "Central Pacific Standard Time";
        case "Antarctica/Mawson": return "West Asia Standard Time";
        case "Antarctica/McMurdo": return "New Zealand Standard Time";
        case "Antarctica/Palmer": return "Pacific SA Standard Time";
        case "Antarctica/Rothera": return "SA Eastern Standard Time";
        case "Antarctica/Syowa": return "E. Africa Standard Time";
        case "Antarctica/Vostok": return "Central Asia Standard Time";
        case "Arctic/Longyearbyen": return "W. Europe Standard Time";
        case "Asia/Aden": return "Arab Standard Time";
        case "Asia/Almaty": return "Central Asia Standard Time";
        case "Asia/Amman": return "Jordan Standard Time";
        case "Asia/Anadyr": return "Russia Time Zone 11";
        case "Asia/Aqtau": return "West Asia Standard Time";
        case "Asia/Aqtobe": return "West Asia Standard Time";
        case "Asia/Ashgabat": return "West Asia Standard Time";
        case "Asia/Baghdad": return "Arabic Standard Time";
        case "Asia/Bahrain": return "Arab Standard Time";
        case "Asia/Baku": return "Azerbaijan Standard Time";
        case "Asia/Bangkok": return "SE Asia Standard Time";
        case "Asia/Barnaul": return "Altai Standard Time";
        case "Asia/Beirut": return "Middle East Standard Time";
        case "Asia/Bishkek": return "Central Asia Standard Time";
        case "Asia/Brunei": return "Singapore Standard Time";
        case "Asia/Calcutta": return "India Standard Time";
        case "Asia/Chita": return "Transbaikal Standard Time";
        case "Asia/Choibalsan": return "Ulaanbaatar Standard Time";
        case "Asia/Colombo": return "Sri Lanka Standard Time";
        case "Asia/Damascus": return "Syria Standard Time";
        case "Asia/Dhaka": return "Bangladesh Standard Time";
        case "Asia/Dili": return "Tokyo Standard Time";
        case "Asia/Dubai": return "Arabian Standard Time";
        case "Asia/Dushanbe": return "West Asia Standard Time";
        case "Asia/Hong_Kong": return "China Standard Time";
        case "Asia/Hovd": return "SE Asia Standard Time";
        case "Asia/Irkutsk": return "North Asia East Standard Time";
        case "Asia/Jakarta": return "SE Asia Standard Time";
        case "Asia/Jayapura": return "Tokyo Standard Time";
        case "Asia/Jerusalem": return "Israel Standard Time";
        case "Asia/Kabul": return "Afghanistan Standard Time";
        case "Asia/Kamchatka": return "Russia Time Zone 11";
        case "Asia/Karachi": return "Pakistan Standard Time";
        case "Asia/Katmandu": return "Nepal Standard Time";
        case "Asia/Khandyga": return "Yakutsk Standard Time";
        case "Asia/Krasnoyarsk": return "North Asia Standard Time";
        case "Asia/Kuala_Lumpur": return "Singapore Standard Time";
        case "Asia/Kuching": return "Singapore Standard Time";
        case "Asia/Kuwait": return "Arab Standard Time";
        case "Asia/Macau": return "China Standard Time";
        case "Asia/Magadan": return "Magadan Standard Time";
        case "Asia/Makassar": return "Singapore Standard Time";
        case "Asia/Manila": return "Singapore Standard Time";
        case "Asia/Muscat": return "Arabian Standard Time";
        case "Asia/Nicosia": return "GTB Standard Time";
        case "Asia/Novokuznetsk": return "North Asia Standard Time";
        case "Asia/Novosibirsk": return "N. Central Asia Standard Time";
        case "Asia/Omsk": return "N. Central Asia Standard Time";
        case "Asia/Oral": return "West Asia Standard Time";
        case "Asia/Phnom_Penh": return "SE Asia Standard Time";
        case "Asia/Pontianak": return "SE Asia Standard Time";
        case "Asia/Pyongyang": return "North Korea Standard Time";
        case "Asia/Qatar": return "Arab Standard Time";
        case "Asia/Qyzylorda": return "Central Asia Standard Time";
        case "Asia/Rangoon": return "Myanmar Standard Time";
        case "Asia/Riyadh": return "Arab Standard Time";
        case "Asia/Saigon": return "SE Asia Standard Time";
        case "Asia/Sakhalin": return "Sakhalin Standard Time";
        case "Asia/Samarkand": return "West Asia Standard Time";
        case "Asia/Seoul": return "Korea Standard Time";
        case "Asia/Shanghai": return "China Standard Time";
        case "Asia/Singapore": return "Singapore Standard Time";
        case "Asia/Srednekolymsk": return "Russia Time Zone 10";
        case "Asia/Taipei": return "Taipei Standard Time";
        case "Asia/Tashkent": return "West Asia Standard Time";
        case "Asia/Tbilisi": return "Georgian Standard Time";
        case "Asia/Tehran": return "Iran Standard Time";
        case "Asia/Thimphu": return "Bangladesh Standard Time";
        case "Asia/Tokyo": return "Tokyo Standard Time";
        case "Asia/Ulaanbaatar": return "Ulaanbaatar Standard Time";
        case "Asia/Urumqi": return "Central Asia Standard Time";
        case "Asia/Ust-Nera": return "Vladivostok Standard Time";
        case "Asia/Vientiane": return "SE Asia Standard Time";
        case "Asia/Vladivostok": return "Vladivostok Standard Time";
        case "Asia/Yakutsk": return "Yakutsk Standard Time";
        case "Asia/Yekaterinburg": return "Ekaterinburg Standard Time";
        case "Asia/Yerevan": return "Caucasus Standard Time";
        case "Atlantic/Azores": return "Azores Standard Time";
        case "Atlantic/Bermuda": return "Atlantic Standard Time";
        case "Atlantic/Canary": return "GMT Standard Time";
        case "Atlantic/Cape_Verde": return "Cape Verde Standard Time";
        case "Atlantic/Faeroe": return "GMT Standard Time";
        case "Atlantic/Madeira": return "GMT Standard Time";
        case "Atlantic/Reykjavik": return "Greenwich Standard Time";
        case "Atlantic/South_Georgia": return "UTC-02";
        case "Atlantic/St_Helena": return "Greenwich Standard Time";
        case "Atlantic/Stanley": return "SA Eastern Standard Time";
        case "Australia/Adelaide": return "Cen. Australia Standard Time";
        case "Australia/Brisbane": return "E. Australia Standard Time";
        case "Australia/Broken_Hill": return "Cen. Australia Standard Time";
        case "Australia/Currie": return "Tasmania Standard Time";
        case "Australia/Darwin": return "AUS Central Standard Time";
        case "Australia/Hobart": return "Tasmania Standard Time";
        case "Australia/Lindeman": return "E. Australia Standard Time";
        case "Australia/Melbourne": return "AUS Eastern Standard Time";
        case "Australia/Perth": return "W. Australia Standard Time";
        case "Australia/Sydney": return "AUS Eastern Standard Time";
        case "CST6CDT": return "Central Standard Time";
        case "EST5EDT": return "Eastern Standard Time";
        case "Etc/GMT": return "UTC";
        case "Etc/GMT+1": return "Cape Verde Standard Time";
        case "Etc/GMT+10": return "Hawaiian Standard Time";
        case "Etc/GMT+11": return "UTC-11";
        case "Etc/GMT+12": return "Dateline Standard Time";
        case "Etc/GMT+2": return "UTC-02";
        case "Etc/GMT+3": return "SA Eastern Standard Time";
        case "Etc/GMT+4": return "SA Western Standard Time";
        case "Etc/GMT+5": return "SA Pacific Standard Time";
        case "Etc/GMT+6": return "Central America Standard Time";
        case "Etc/GMT+7": return "US Mountain Standard Time";
        case "Etc/GMT-1": return "W. Central Africa Standard Time";
        case "Etc/GMT-10": return "West Pacific Standard Time";
        case "Etc/GMT-11": return "Central Pacific Standard Time";
        case "Etc/GMT-12": return "UTC+12";
        case "Etc/GMT-13": return "Tonga Standard Time";
        case "Etc/GMT-14": return "Line Islands Standard Time";
        case "Etc/GMT-2": return "South Africa Standard Time";
        case "Etc/GMT-3": return "E. Africa Standard Time";
        case "Etc/GMT-4": return "Arabian Standard Time";
        case "Etc/GMT-5": return "West Asia Standard Time";
        case "Etc/GMT-6": return "Central Asia Standard Time";
        case "Etc/GMT-7": return "SE Asia Standard Time";
        case "Etc/GMT-8": return "Singapore Standard Time";
        case "Etc/GMT-9": return "Tokyo Standard Time";
        case "Europe/Amsterdam": return "W. Europe Standard Time";
        case "Europe/Andorra": return "W. Europe Standard Time";
        case "Europe/Astrakhan": return "Astrakhan Standard Time";
        case "Europe/Athens": return "GTB Standard Time";
        case "Europe/Belgrade": return "Central Europe Standard Time";
        case "Europe/Berlin": return "W. Europe Standard Time";
        case "Europe/Bratislava": return "Central Europe Standard Time";
        case "Europe/Brussels": return "Romance Standard Time";
        case "Europe/Bucharest": return "GTB Standard Time";
        case "Europe/Budapest": return "Central Europe Standard Time";
        case "Europe/Busingen": return "W. Europe Standard Time";
        case "Europe/Chisinau": return "GTB Standard Time";
        case "Europe/Copenhagen": return "Romance Standard Time";
        case "Europe/Dublin": return "GMT Standard Time";
        case "Europe/Gibraltar": return "W. Europe Standard Time";
        case "Europe/Guernsey": return "GMT Standard Time";
        case "Europe/Helsinki": return "FLE Standard Time";
        case "Europe/Isle_of_Man": return "GMT Standard Time";
        case "Europe/Istanbul": return "Turkey Standard Time";
        case "Europe/Jersey": return "GMT Standard Time";
        case "Europe/Kaliningrad": return "Kaliningrad Standard Time";
        case "Europe/Kiev": return "FLE Standard Time";
        case "Europe/Lisbon": return "GMT Standard Time";
        case "Europe/Ljubljana": return "Central Europe Standard Time";
        case "Europe/London": return "GMT Standard Time";
        case "Europe/Luxembourg": return "W. Europe Standard Time";
        case "Europe/Madrid": return "Romance Standard Time";
        case "Europe/Malta": return "W. Europe Standard Time";
        case "Europe/Mariehamn": return "FLE Standard Time";
        case "Europe/Minsk": return "Belarus Standard Time";
        case "Europe/Monaco": return "W. Europe Standard Time";
        case "Europe/Moscow": return "Russian Standard Time";
        case "Europe/Oslo": return "W. Europe Standard Time";
        case "Europe/Paris": return "Romance Standard Time";
        case "Europe/Podgorica": return "Central Europe Standard Time";
        case "Europe/Prague": return "Central Europe Standard Time";
        case "Europe/Riga": return "FLE Standard Time";
        case "Europe/Rome": return "W. Europe Standard Time";
        case "Europe/Samara": return "Russia Time Zone 3";
        case "Europe/San_Marino": return "W. Europe Standard Time";
        case "Europe/Sarajevo": return "Central European Standard Time";
        case "Europe/Simferopol": return "Russian Standard Time";
        case "Europe/Skopje": return "Central European Standard Time";
        case "Europe/Sofia": return "FLE Standard Time";
        case "Europe/Stockholm": return "W. Europe Standard Time";
        case "Europe/Tallinn": return "FLE Standard Time";
        case "Europe/Tirane": return "Central Europe Standard Time";
        case "Europe/Uzhgorod": return "FLE Standard Time";
        case "Europe/Vaduz": return "W. Europe Standard Time";
        case "Europe/Vatican": return "W. Europe Standard Time";
        case "Europe/Vienna": return "W. Europe Standard Time";
        case "Europe/Vilnius": return "FLE Standard Time";
        case "Europe/Volgograd": return "Russian Standard Time";
        case "Europe/Warsaw": return "Central European Standard Time";
        case "Europe/Zagreb": return "Central European Standard Time";
        case "Europe/Zaporozhye": return "FLE Standard Time";
        case "Europe/Zurich": return "W. Europe Standard Time";
        case "Indian/Antananarivo": return "E. Africa Standard Time";
        case "Indian/Chagos": return "Central Asia Standard Time";
        case "Indian/Christmas": return "SE Asia Standard Time";
        case "Indian/Cocos": return "Myanmar Standard Time";
        case "Indian/Comoro": return "E. Africa Standard Time";
        case "Indian/Kerguelen": return "West Asia Standard Time";
        case "Indian/Mahe": return "Mauritius Standard Time";
        case "Indian/Maldives": return "West Asia Standard Time";
        case "Indian/Mauritius": return "Mauritius Standard Time";
        case "Indian/Mayotte": return "E. Africa Standard Time";
        case "Indian/Reunion": return "Mauritius Standard Time";
        case "MST7MDT": return "Mountain Standard Time";
        case "PST8PDT": return "Pacific Standard Time";
        case "Pacific/Apia": return "Samoa Standard Time";
        case "Pacific/Auckland": return "New Zealand Standard Time";
        case "Pacific/Bougainville": return "Central Pacific Standard Time";
        case "Pacific/Easter": return "Easter Island Standard Time";
        case "Pacific/Efate": return "Central Pacific Standard Time";
        case "Pacific/Enderbury": return "Tonga Standard Time";
        case "Pacific/Fakaofo": return "Tonga Standard Time";
        case "Pacific/Fiji": return "Fiji Standard Time";
        case "Pacific/Funafuti": return "UTC+12";
        case "Pacific/Galapagos": return "Central America Standard Time";
        case "Pacific/Guadalcanal": return "Central Pacific Standard Time";
        case "Pacific/Guam": return "West Pacific Standard Time";
        case "Pacific/Honolulu": return "Hawaiian Standard Time";
        case "Pacific/Johnston": return "Hawaiian Standard Time";
        case "Pacific/Kiritimati": return "Line Islands Standard Time";
        case "Pacific/Kosrae": return "Central Pacific Standard Time";
        case "Pacific/Kwajalein": return "UTC+12";
        case "Pacific/Majuro": return "UTC+12";
        case "Pacific/Midway": return "UTC-11";
        case "Pacific/Nauru": return "UTC+12";
        case "Pacific/Niue": return "UTC-11";
        case "Pacific/Noumea": return "Central Pacific Standard Time";
        case "Pacific/Pago_Pago": return "UTC-11";
        case "Pacific/Palau": return "Tokyo Standard Time";
        case "Pacific/Ponape": return "Central Pacific Standard Time";
        case "Pacific/Port_Moresby": return "West Pacific Standard Time";
        case "Pacific/Rarotonga": return "Hawaiian Standard Time";
        case "Pacific/Saipan": return "West Pacific Standard Time";
        case "Pacific/Tahiti": return "Hawaiian Standard Time";
        case "Pacific/Tarawa": return "UTC+12";
        case "Pacific/Tongatapu": return "Tonga Standard Time";
        case "Pacific/Truk": return "West Pacific Standard Time";
        case "Pacific/Wake": return "UTC+12";
        case "Pacific/Wallis": return "UTC+12";
        default: return null;
    }
}

version(Windows) version(UpdateWindowsTZTranslations) unittest
{
    import std.stdio : stderr;

    foreach(tzName; TimeZone.getInstalledTZNames())
    {
        if (tzDatabaseNameToWindowsTZName(tzName) is null)
            stderr.writeln("Missing TZName to Windows translation: ", tzName);
    }
}


/++
    $(RED Please use $(LREF parseTZConversions) instead. This function will be
          deprecated in 2.072, because Microsoft changes their time zones too
          often for us to compile the conversions into Phobos and have them be
          properly up-to-date.)

    Converts the given Windows time zone name to a corresponding TZ Database
    name.

    Returns null if the given time zone name cannot be converted.

    See_Also:
        $(WEB unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html,
              Windows <-> TZ Database Name Conversion Table)

    Params:
        tzName = The TZ Database name to convert.
  +/
string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc
{
    switch(tzName)
    {
        case "AUS Central Standard Time": return "Australia/Darwin";
        case "AUS Eastern Standard Time": return "Australia/Sydney";
        case "Afghanistan Standard Time": return "Asia/Kabul";
        case "Haiti Standard Time": return "America/Port-au-Prince";
        case "Alaskan Standard Time": return "America/Anchorage";
        case "Altai Standard Time": return "Asia/Barnaul";
        case "Arab Standard Time": return "Asia/Riyadh";
        case "Arabian Standard Time": return "Asia/Dubai";
        case "Arabic Standard Time": return "Asia/Baghdad";
        case "Argentina Standard Time": return "America/Buenos_Aires";
        case "Astrakhan Standard Time": return "Europe/Astrakhan";
        case "Atlantic Standard Time": return "America/Halifax";
        case "Azerbaijan Standard Time": return "Asia/Baku";
        case "Azores Standard Time": return "Atlantic/Azores";
        case "Bahia Standard Time": return "America/Bahia";
        case "Bangladesh Standard Time": return "Asia/Dhaka";
        case "Belarus Standard Time": return "Europe/Minsk";
        case "Canada Central Standard Time": return "America/Regina";
        case "Cape Verde Standard Time": return "Atlantic/Cape_Verde";
        case "Caucasus Standard Time": return "Asia/Yerevan";
        case "Cen. Australia Standard Time": return "Australia/Adelaide";
        case "Central America Standard Time": return "America/Guatemala";
        case "Central Asia Standard Time": return "Asia/Almaty";
        case "Central Brazilian Standard Time": return "America/Cuiaba";
        case "Central Europe Standard Time": return "Europe/Budapest";
        case "Central European Standard Time": return "Europe/Warsaw";
        case "Central Pacific Standard Time": return "Pacific/Guadalcanal";
        case "Central Standard Time": return "America/Chicago";
        case "Central Standard Time (Mexico)": return "America/Mexico_City";
        case "China Standard Time": return "Asia/Shanghai";
        case "Dateline Standard Time": return "Etc/GMT+12";
        case "E. Africa Standard Time": return "Africa/Nairobi";
        case "E. Australia Standard Time": return "Australia/Brisbane";
        // This doesn't appear to be in the current stuff from MS, but the autotester
        // is failing without it (probably because its time zone data hasn't been
        // updated recently enough).
        case "E. Europe Standard Time": return "Europe/Minsk";
        case "E. South America Standard Time": return "America/Sao_Paulo";
        case "Easter Island Standard Time": return "Pacific/Easter";
        case "Eastern Standard Time": return "America/New_York";
        case "Eastern Standard Time (Mexico)": return "America/Cancun";
        case "Egypt Standard Time": return "Africa/Cairo";
        case "Ekaterinburg Standard Time": return "Asia/Yekaterinburg";
        case "FLE Standard Time": return "Europe/Kiev";
        case "Fiji Standard Time": return "Pacific/Fiji";
        case "GMT Standard Time": return "Europe/London";
        case "GTB Standard Time": return "Europe/Athens";
        case "Georgian Standard Time": return "Asia/Tbilisi";
        case "Greenland Standard Time": return "America/Godthab";
        case "Greenwich Standard Time": return "Atlantic/Reykjavik";
        case "Hawaiian Standard Time": return "Pacific/Honolulu";
        case "India Standard Time": return "Asia/Calcutta";
        case "Iran Standard Time": return "Asia/Tehran";
        case "Israel Standard Time": return "Asia/Jerusalem";
        case "Jordan Standard Time": return "Asia/Amman";
        case "Kaliningrad Standard Time": return "Europe/Kaliningrad";
        // Same as with E. Europe Standard Time.
        case "Kamchatka Standard Time": return "Asia/Kamchatka";
        case "Korea Standard Time": return "Asia/Seoul";
        case "Libya Standard Time": return "Africa/Tripoli";
        case "Line Islands Standard Time": return "Pacific/Kiritimati";
        case "Magadan Standard Time": return "Asia/Magadan";
        case "Mauritius Standard Time": return "Indian/Mauritius";
        // Same as with E. Europe Standard Time.
        case "Mexico Standard Time": return "America/Mexico_City";
        // Same as with E. Europe Standard Time.
        case "Mexico Standard Time 2": return "America/Chihuahua";
        // Same as with E. Europe Standard Time.
        case "Mid-Atlantic Standard Time": return "Etc/GMT+2";
        case "Middle East Standard Time": return "Asia/Beirut";
        case "Montevideo Standard Time": return "America/Montevideo";
        case "Morocco Standard Time": return "Africa/Casablanca";
        case "Mountain Standard Time": return "America/Denver";
        case "Mountain Standard Time (Mexico)": return "America/Chihuahua";
        case "Myanmar Standard Time": return "Asia/Rangoon";
        case "N. Central Asia Standard Time": return "Asia/Novosibirsk";
        case "Namibia Standard Time": return "Africa/Windhoek";
        case "Nepal Standard Time": return "Asia/Katmandu";
        case "New Zealand Standard Time": return "Pacific/Auckland";
        case "Newfoundland Standard Time": return "America/St_Johns";
        case "North Asia East Standard Time": return "Asia/Irkutsk";
        case "North Asia Standard Time": return "Asia/Krasnoyarsk";
        case "North Korea Standard Time": return "Asia/Pyongyang";
        case "Pacific SA Standard Time": return "America/Santiago";
        case "Pacific Standard Time": return "America/Los_Angeles";
        case "Pacific Standard Time (Mexico)": return "America/Santa_Isabel";
        case "Pakistan Standard Time": return "Asia/Karachi";
        case "Paraguay Standard Time": return "America/Asuncion";
        case "Romance Standard Time": return "Europe/Paris";
        case "Russia Time Zone 10": return "Asia/Srednekolymsk";
        case "Russia Time Zone 11": return "Asia/Anadyr";
        case "Russia Time Zone 3": return "Europe/Samara";
        case "Russian Standard Time": return "Europe/Moscow";
        case "SA Eastern Standard Time": return "America/Cayenne";
        case "SA Pacific Standard Time": return "America/Bogota";
        case "SA Western Standard Time": return "America/La_Paz";
        case "SE Asia Standard Time": return "Asia/Bangkok";
        case "Sakhalin Standard Time": return "Asia/Sakhalin";
        case "Samoa Standard Time": return "Pacific/Apia";
        case "Singapore Standard Time": return "Asia/Singapore";
        case "South Africa Standard Time": return "Africa/Johannesburg";
        case "Sri Lanka Standard Time": return "Asia/Colombo";
        case "Syria Standard Time": return "Asia/Damascus";
        case "Taipei Standard Time": return "Asia/Taipei";
        case "Tasmania Standard Time": return "Australia/Hobart";
        case "Tokyo Standard Time": return "Asia/Tokyo";
        case "Tonga Standard Time": return "Pacific/Tongatapu";
        case "Transbaikal Standard Time": return "Asia/Chita";
        case "Turkey Standard Time": return "Europe/Istanbul";
        case "US Eastern Standard Time": return "America/Indianapolis";
        case "US Mountain Standard Time": return "America/Phoenix";
        case "UTC": return "Etc/GMT";
        case "UTC+12": return "Etc/GMT-12";
        case "UTC-02": return "Etc/GMT+2";
        case "UTC-11": return "Etc/GMT+11";
        case "Ulaanbaatar Standard Time": return "Asia/Ulaanbaatar";
        case "Venezuela Standard Time": return "America/Caracas";
        case "Vladivostok Standard Time": return "Asia/Vladivostok";
        case "W. Australia Standard Time": return "Australia/Perth";
        case "W. Central Africa Standard Time": return "Africa/Lagos";
        case "W. Europe Standard Time": return "Europe/Berlin";
        case "West Asia Standard Time": return "Asia/Tashkent";
        case "West Pacific Standard Time": return "Pacific/Port_Moresby";
        case "Yakutsk Standard Time": return "Asia/Yakutsk";
        default: return null;
    }
}

version(Windows) version(UpdateWindowsTZTranslations) unittest
{
    import std.stdio : stderr;

    foreach(winName; WindowsTimeZone.getInstalledTZNames())
    {
        if (windowsTZNameToTZDatabaseName(winName) is null)
            stderr.writeln("Missing Windows to TZName translation: ", winName);
    }
}


//==============================================================================
// Section with StopWatch and Benchmark Code.
//==============================================================================

/++
   $(D StopWatch) measures time as precisely as possible.

   This class uses a high-performance counter. On Windows systems, it uses
   $(D QueryPerformanceCounter), and on Posix systems, it uses
   $(D clock_gettime) if available, and $(D gettimeofday) otherwise.

   But the precision of $(D StopWatch) differs from system to system. It is
   impossible to for it to be the same from system to system since the precision
   of the system clock varies from system to system, and other system-dependent
   and situation-dependent stuff (such as the overhead of a context switch
   between threads) can also affect $(D StopWatch)'s accuracy.
  +/
@safe struct StopWatch
{
public:

    /++
       Auto start with constructor.
      +/
    this(AutoStart autostart)
    {
        if(autostart)
            start();
    }

    @safe unittest
    {
        auto sw = StopWatch(AutoStart.yes);
        sw.stop();
    }


    ///
    bool opEquals(const StopWatch rhs) const pure nothrow
    {
        return opEquals(rhs);
    }

    /// ditto
    bool opEquals(const ref StopWatch rhs) const pure nothrow
    {
        return _timeStart == rhs._timeStart &&
               _timeMeasured == rhs._timeMeasured;
    }


    /++
       Resets the stop watch.
      +/
    void reset()
    {
        if(_flagStarted)
        {
            // Set current system time if StopWatch is measuring.
            _timeStart = TickDuration.currSystemTick;
        }
        else
        {
            // Set zero if StopWatch is not measuring.
            _timeStart.length = 0;
        }

        _timeMeasured.length = 0;
    }

    ///
    @safe unittest
    {
        StopWatch sw;
        sw.start();
        sw.stop();
        sw.reset();
        assert(sw.peek().to!("seconds", real)() == 0);
    }


    /++
       Starts the stop watch.
      +/
    void start()
    {
        assert(!_flagStarted);
        _flagStarted = true;
        _timeStart = TickDuration.currSystemTick;
    }

    @trusted unittest
    {
        StopWatch sw;
        sw.start();
        auto t1 = sw.peek();
        bool doublestart = true;
        try
            sw.start();
        catch(AssertError e)
            doublestart = false;
        assert(!doublestart);
        sw.stop();
        assert((t1 - sw.peek()).to!("seconds", real)() <= 0);
    }


    /++
       Stops the stop watch.
      +/
    void stop()
    {
        assert(_flagStarted);
        _flagStarted = false;
        _timeMeasured += TickDuration.currSystemTick - _timeStart;
    }

    @trusted unittest
    {
        StopWatch sw;
        sw.start();
        sw.stop();
        auto t1 = sw.peek();
        bool doublestop = true;
        try
            sw.stop();
        catch(AssertError e)
            doublestop = false;
        assert(!doublestop);
        assert((t1 - sw.peek()).to!("seconds", real)() == 0);
    }


    /++
       Peek at the amount of time which has passed since the stop watch was
       started.
      +/
    TickDuration peek() const
    {
        if(_flagStarted)
            return TickDuration.currSystemTick - _timeStart + _timeMeasured;

        return _timeMeasured;
    }

    @safe unittest
    {
        StopWatch sw;
        sw.start();
        auto t1 = sw.peek();
        sw.stop();
        auto t2 = sw.peek();
        auto t3 = sw.peek();
        assert(t1 <= t2);
        assert(t2 == t3);
    }


    /++
       Set the amount of time which has been measured since the stop watch was
       started.
      +/
    void setMeasured(TickDuration d)
    {
        reset();
        _timeMeasured = d;
    }

     @safe unittest
    {
        StopWatch sw;
        TickDuration t0;
        t0.length = 100;
        sw.setMeasured(t0);
        auto t1 = sw.peek();
        assert(t0 == t1);
    }


    /++
       Confirm whether this stopwatch is measuring time.
      +/
    bool running() @property const pure nothrow
    {
        return _flagStarted;
    }

    @safe unittest
    {
        StopWatch sw1;
        assert(!sw1.running);
        sw1.start();
        assert(sw1.running);
        sw1.stop();
        assert(!sw1.running);
        StopWatch sw2 = AutoStart.yes;
        assert(sw2.running);
        sw2.stop();
        assert(!sw2.running);
        sw2.start();
        assert(sw2.running);
    }




private:

    // true if observing.
    bool _flagStarted = false;

    // TickDuration at the time of StopWatch starting measurement.
    TickDuration _timeStart;

    // Total time that StopWatch ran.
    TickDuration _timeMeasured;
}

///
@safe unittest
{
    void writeln(S...)(S args){}
    static void bar() {}

    StopWatch sw;
    enum n = 100;
    TickDuration[n] times;
    TickDuration last = TickDuration.from!"seconds"(0);
    foreach(i; 0..n)
    {
       sw.start(); //start/resume mesuring.
       foreach(unused; 0..1_000_000)
           bar();
       sw.stop();  //stop/pause measuring.
       //Return value of peek() after having stopped are the always same.
       writeln((i + 1) * 1_000_000, " times done, lap time: ",
               sw.peek().msecs, "[ms]");
       times[i] = sw.peek() - last;
       last = sw.peek();
    }
    real sum = 0;
    // To get the number of seconds,
    // use properties of TickDuration.
    // (seconds, msecs, usecs, hnsecs)
    foreach(t; times)
       sum += t.hnsecs;
    writeln("Average time: ", sum/n, " hnsecs");
}


/++
    Benchmarks code for speed assessment and comparison.

    Params:
        fun = aliases of callable objects (e.g. function names). Each should
              take no arguments.
        n   = The number of times each function is to be executed.

    Returns:
        The amount of time (as a $(CXREF time, TickDuration)) that it took to
        call each function $(D n) times. The first value is the length of time
        that it took to call $(D fun[0]) $(D n) times. The second value is the
        length of time it took to call $(D fun[1]) $(D n) times. Etc.

    Note that casting the TickDurations to $(CXREF time, Duration)s will make
    the results easier to deal with (and it may change in the future that
    benchmark will return an array of Durations rather than TickDurations).

    See_Also:
        $(LREF measureTime)
  +/
TickDuration[fun.length] benchmark(fun...)(uint n)
{
    TickDuration[fun.length] result;
    StopWatch sw;
    sw.start();

    foreach(i, unused; fun)
    {
        sw.reset();
        foreach(j; 0 .. n)
            fun[i]();
        result[i] = sw.peek();
    }

    return result;
}

///
unittest
{
    import std.conv : to;
    int a;
    void f0() {}
    void f1() {auto b = a;}
    void f2() {auto b = to!string(a);}
    auto r = benchmark!(f0, f1, f2)(10_000);
    auto f0Result = to!Duration(r[0]); // time f0 took to run 10,000 times
    auto f1Result = to!Duration(r[1]); // time f1 took to run 10,000 times
    auto f2Result = to!Duration(r[2]); // time f2 took to run 10,000 times
}

@safe unittest
{
    int a;
    void f0() {}
    //void f1() {auto b = to!(string)(a);}
    void f2() {auto b = (a);}
    auto r = benchmark!(f0, f2)(100);
}


/++
   Return value of benchmark with two functions comparing.
  +/
@safe struct ComparingBenchmarkResult
{
    /++
       Evaluation value

       This returns the evaluation value of performance as the ratio of
       baseFunc's time over targetFunc's time. If performance is high, this
       returns a high value.
      +/
    @property real point() const pure nothrow
    {
        return _baseTime.length / cast(const real)_targetTime.length;
    }


    /++
       The time required of the base function
      +/
    @property public TickDuration baseTime() const pure nothrow
    {
        return _baseTime;
    }


    /++
       The time required of the target function
      +/
    @property public TickDuration targetTime() const pure nothrow
    {
        return _targetTime;
    }

private:

    this(TickDuration baseTime, TickDuration targetTime) pure nothrow
    {
        _baseTime = baseTime;
        _targetTime = targetTime;
    }

    TickDuration _baseTime;
    TickDuration _targetTime;
}


/++
   Benchmark with two functions comparing.

   Params:
       baseFunc   = The function to become the base of the speed.
       targetFunc = The function that wants to measure speed.
       times      = The number of times each function is to be executed.
  +/
ComparingBenchmarkResult comparingBenchmark(alias baseFunc,
                                            alias targetFunc,
                                            int times = 0xfff)()
{
    auto t = benchmark!(baseFunc, targetFunc)(times);
    return ComparingBenchmarkResult(t[0], t[1]);
}

///
@safe unittest
{
    void f1x() {}
    void f2x() {}
    @safe void f1o() {}
    @safe void f2o() {}
    auto b1 = comparingBenchmark!(f1o, f2o, 1)(); // OK
    //writeln(b1.point);
}

//Bug# 8450
unittest
{
    @safe    void safeFunc() {}
    @trusted void trustFunc() {}
    @system  void sysFunc() {}
    auto safeResult  = comparingBenchmark!((){safeFunc();}, (){safeFunc();})();
    auto trustResult = comparingBenchmark!((){trustFunc();}, (){trustFunc();})();
    auto sysResult   = comparingBenchmark!((){sysFunc();}, (){sysFunc();})();
    auto mixedResult1  = comparingBenchmark!((){safeFunc();}, (){trustFunc();})();
    auto mixedResult2  = comparingBenchmark!((){trustFunc();}, (){sysFunc();})();
    auto mixedResult3  = comparingBenchmark!((){safeFunc();}, (){sysFunc();})();
}


//==============================================================================
// Section with public helper functions and templates.
//==============================================================================


/++
    Whether the given type defines all of the necessary functions for it to
    function as a time point.

    1. $(D T) must define a static property named $(D min) which is the smallest
       value of $(D T) as $(Unqual!T).

    2. $(D T) must define a static property named $(D max) which is the largest
       value of $(D T) as $(Unqual!T).

    3. $(D T) must define an $(D opBinary) for addition and subtraction that
       accepts $(CXREF time, Duration) and returns $(D Unqual!T).

    4. $(D T) must define an $(D opOpAssign) for addition and subtraction that
       accepts $(CXREF time, Duration) and returns $(D ref Unqual!T).

    5. $(D T) must define a $(D opBinary) for subtraction which accepts $(D T)
       and returns returns $(CXREF time, Duration).
  +/
template isTimePoint(T)
{
    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(
                                             {
                                                 // Until the overload with TickDuration is removed, this is ambiguous.
                                                 //alias add = U.opOpAssign!"+";
                                                 //alias sub = U.opOpAssign!"-";
                                                 U u;
                                                 auto ref add() { return u += Duration.init; }
                                                 auto ref sub() { return u -= Duration.init; }
                                                 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);
}

///
unittest
{
    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));
}

unittest
{
    import std.meta : AliasSeq;

    foreach(TP; AliasSeq!(Date, DateTime, SysTime, TimeOfDay))
    {
        static assert(isTimePoint!(const TP), TP.stringof);
        static assert(isTimePoint!(immutable TP), TP.stringof);
    }

    foreach(T; AliasSeq!(float, string, Duration, Interval!Date, PosInfInterval!Date, NegInfInterval!Date))
        static assert(!isTimePoint!T, T.stringof);
}


/++
    Whether the given Gregorian Year is a leap year.

    Params:
        year = The year to to be tested.
 +/
static bool yearIsLeapYear(int year) @safe pure nothrow
{
    if(year % 400 == 0)
        return true;

    if(year % 100 == 0)
        return false;

    return year % 4 == 0;
}

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));
    }
}

/++
    Converts from unix time (which uses midnight, January 1st, 1970 UTC as its
    epoch and seconds as its units) to "std time" (which uses midnight,
    January 1st, 1 A.D. UTC and hnsecs as its units).

    The C standard does not specify the representation of time_t, so it is
    implementation defined. On POSIX systems, unix time is equivalent to
    time_t, but that's not necessarily true on other systems (e.g. it is
    not true for the Digital Mars C runtime). So, be careful when using unix
    time with C functions on non-POSIX systems.

    "std time"'s epoch is based on the Proleptic Gregorian Calendar per ISO
    8601 and is what $(LREF SysTime) uses internally. However, holding the time
    as an integer in hnescs since that epoch technically isn't actually part of
    the standard, much as it's based on it, so the name "std time" isn't
    particularly good, but there isn't an official name for it. C# uses "ticks"
    for the same thing, but they aren't actually clock ticks, and the term
    "ticks" $(I is) used for actual clock ticks for $(CXREF time, MonoTime), so
    it didn't make sense to use the term ticks here. So, for better or worse,
    std.datetime uses the term "std time" for this.

    Params:
        unixTime = The unix time to convert.

    See_Also:
        SysTime.fromUnixTime
  +/
long unixTimeToStdTime(long unixTime) @safe pure nothrow
{
    return 621_355_968_000_000_000L + convert!("seconds", "hnsecs")(unixTime);
}

///
unittest
{
    // Midnight, January 1st, 1970
    assert(unixTimeToStdTime(0) == 621_355_968_000_000_000L);
    assert(SysTime(unixTimeToStdTime(0)) ==
           SysTime(DateTime(1970, 1, 1), UTC()));

    assert(unixTimeToStdTime(int.max) == 642_830_804_470_000_000L);
    assert(SysTime(unixTimeToStdTime(int.max)) ==
           SysTime(DateTime(2038, 1, 19, 3, 14, 07), UTC()));

    assert(unixTimeToStdTime(-127_127) == 621_354_696_730_000_000L);
    assert(SysTime(unixTimeToStdTime(-127_127)) ==
           SysTime(DateTime(1969, 12, 30, 12, 41, 13), UTC()));
}

unittest
{
    // Midnight, January 2nd, 1970
    assert(unixTimeToStdTime(86_400) == 621_355_968_000_000_000L + 864_000_000_000L);
    // Midnight, December 31st, 1969
    assert(unixTimeToStdTime(-86_400) == 621_355_968_000_000_000L - 864_000_000_000L);

    assert(unixTimeToStdTime(0) == (Date(1970, 1, 1) - Date(1, 1, 1)).total!"hnsecs");
    assert(unixTimeToStdTime(0) == (DateTime(1970, 1, 1) - DateTime(1, 1, 1)).total!"hnsecs");

    foreach(dt; [DateTime(2010, 11, 1, 19, 5, 22), DateTime(1952, 7, 6, 2, 17, 9)])
        assert(unixTimeToStdTime((dt - DateTime(1970, 1, 1)).total!"seconds") == (dt - DateTime.init).total!"hnsecs");
}


/++
    Converts std time (which uses midnight, January 1st, 1 A.D. UTC as its epoch
    and hnsecs as its units) to unix time (which uses midnight, January 1st,
    1970 UTC as its epoch and seconds as its units).

    The C standard does not specify the representation of time_t, so it is
    implementation defined. On POSIX systems, unix time is equivalent to
    time_t, but that's not necessarily true on other systems (e.g. it is
    not true for the Digital Mars C runtime). So, be careful when using unix
    time with C functions on non-POSIX systems.

    "std time"'s epoch is based on the Proleptic Gregorian Calendar per ISO
    8601 and is what $(LREF SysTime) uses internally. However, holding the time
    as an integer in hnescs since that epoch technically isn't actually part of
    the standard, much as it's based on it, so the name "std time" isn't
    particularly good, but there isn't an official name for it. C# uses "ticks"
    for the same thing, but they aren't actually clock ticks, and the term
    "ticks" $(I is) used for actual clock ticks for $(CXREF time, MonoTime), so
    it didn't make sense to use the term ticks here. So, for better or worse,
    std.datetime uses the term "std time" for this.

    By default, the return type is time_t (which is normally an alias for
    int on 32-bit systems and long on 64-bit systems), but if a different
    size is required than either int or long can be passed as a template
    argument to get the desired size.

    If the return type is int, and the result can't fit in an int, then the
    closest value that can be held in 32 bits will be used (so $(D int.max)
    if it goes over and $(D int.min) if it goes under). However, no attempt
    is made to deal with integer overflow if the return type is long.

    Params:
        T = The return type (int or long). It defaults to time_t, which is
            normally 32 bits on a 32-bit system and 64 bits on a 64-bit
            system.
        stdTime = The std time to convert.

    Returns:
        A signed integer representing the unix time which is equivalent to
        the given std time.

    See_Also:
        SysTime.toUnixTime
  +/
T stdTimeToUnixTime(T = time_t)(long stdTime) @safe pure nothrow
    if(is(T == int) || is(T == long))
{
    immutable unixTime = convert!("hnsecs", "seconds")(stdTime - 621_355_968_000_000_000L);

    static assert(is(time_t == int) || is(time_t == long),
                  "Currently, std.datetime only supports systems where time_t is int or long");

    static if(is(T == long))
        return unixTime;
    else static if(is(T == int))
    {
        if(unixTime > int.max)
            return int.max;
        return unixTime < int.min ? int.min : cast(int)unixTime;
    }
    else static assert(0, "Bug in template constraint. Only int and long allowed.");
}

///
unittest
{
    // Midnight, January 1st, 1970 UTC
    assert(stdTimeToUnixTime(621_355_968_000_000_000L) == 0);

    // 2038-01-19 03:14:07 UTC
    assert(stdTimeToUnixTime(642_830_804_470_000_000L) == int.max);
}

unittest
{
    enum unixEpochAsStdTime = (Date(1970, 1, 1) - Date.init).total!"hnsecs";

    assert(stdTimeToUnixTime(unixEpochAsStdTime) == 0);  //Midnight, January 1st, 1970
    assert(stdTimeToUnixTime(unixEpochAsStdTime + 864_000_000_000L) == 86_400);  //Midnight, January 2nd, 1970
    assert(stdTimeToUnixTime(unixEpochAsStdTime - 864_000_000_000L) == -86_400);  //Midnight, December 31st, 1969

    assert(stdTimeToUnixTime((Date(1970, 1, 1) - Date(1, 1, 1)).total!"hnsecs") == 0);
    assert(stdTimeToUnixTime((DateTime(1970, 1, 1) - DateTime(1, 1, 1)).total!"hnsecs") == 0);

    foreach(dt; [DateTime(2010, 11, 1, 19, 5, 22), DateTime(1952, 7, 6, 2, 17, 9)])
        assert(stdTimeToUnixTime((dt - DateTime.init).total!"hnsecs") == (dt - DateTime(1970, 1, 1)).total!"seconds");

    enum max = convert!("seconds", "hnsecs")(int.max);
    enum min = convert!("seconds", "hnsecs")(int.min);
    enum one = convert!("seconds", "hnsecs")(1);

    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max) == int.max);
    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max) == int.max);

    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max + one) == int.max + 1L);
    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max + one) == int.max);
    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max + 9_999_999) == int.max);
    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max + 9_999_999) == int.max);

    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min) == int.min);
    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min) == int.min);

    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min - one) == int.min - 1L);
    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min - one) == int.min);
    assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min - 9_999_999) == int.min);
    assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min - 9_999_999) == int.min);
}


version(StdDdoc)
{
    version(Windows) {}
    else
    {
        alias SYSTEMTIME = void*;
        alias FILETIME = void*;
    }

    /++
        $(BLUE This function is Windows-Only.)

        Converts a $(D SYSTEMTIME) struct to a $(LREF SysTime).

        Params:
            st = The $(D SYSTEMTIME) struct to convert.
            tz = The time zone that the time in the $(D SYSTEMTIME) struct is
                 assumed to be (if the $(D SYSTEMTIME) was supplied by a Windows
                 system call, the $(D SYSTEMTIME) will either be in local time
                 or UTC, depending on the call).

        Throws:
            $(LREF DateTimeException) if the given $(D SYSTEMTIME) will not fit in
            a $(LREF SysTime), which is highly unlikely to happen given that
            $(D SysTime.max) is in 29,228 A.D. and the maximum $(D SYSTEMTIME)
            is in 30,827 A.D.
      +/
    SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe;


    /++
        $(BLUE This function is Windows-Only.)

        Converts a $(LREF SysTime) to a $(D SYSTEMTIME) struct.

        The $(D SYSTEMTIME) which is returned will be set using the given
        $(LREF SysTime)'s time zone, so to get the $(D SYSTEMTIME) in
        UTC, set the $(LREF SysTime)'s time zone to UTC.

        Params:
            sysTime = The $(LREF SysTime) to convert.

        Throws:
            $(LREF DateTimeException) if the given $(LREF SysTime) will not fit in a
            $(D SYSTEMTIME). This will only happen if the $(LREF SysTime)'s date is
            prior to 1601 A.D.
      +/
    SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe;


    /++
        $(BLUE This function is Windows-Only.)

        Converts a $(D FILETIME) struct to the number of hnsecs since midnight,
        January 1st, 1 A.D.

        Params:
            ft = The $(D FILETIME) struct to convert.

        Throws:
            $(LREF DateTimeException) if the given $(D FILETIME) cannot be
            represented as the return value.
      +/
    long FILETIMEToStdTime(const FILETIME* ft) @safe;


    /++
        $(BLUE This function is Windows-Only.)

        Converts a $(D FILETIME) struct to a $(LREF SysTime).

        Params:
            ft = The $(D FILETIME) struct to convert.
            tz = The time zone that the $(LREF SysTime) will be in ($(D FILETIME)s
                 are in UTC).

        Throws:
            $(LREF DateTimeException) if the given $(D FILETIME) will not fit in a
            $(LREF SysTime).
      +/
    SysTime FILETIMEToSysTime(const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe;


    /++
        $(BLUE This function is Windows-Only.)

        Converts a number of hnsecs since midnight, January 1st, 1 A.D. to a
        $(D FILETIME) struct.

        Params:
            stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. UTC.

        Throws:
            $(LREF DateTimeException) if the given value will not fit in a
            $(D FILETIME).
      +/
    FILETIME stdTimeToFILETIME(long stdTime) @safe;


    /++
        $(BLUE This function is Windows-Only.)

        Converts a $(LREF SysTime) to a $(D FILETIME) struct.

        $(D FILETIME)s are always in UTC.

        Params:
            sysTime = The $(LREF SysTime) to convert.

        Throws:
            $(LREF DateTimeException) if the given $(LREF SysTime) will not fit in a
            $(D FILETIME).
      +/
    FILETIME SysTimeToFILETIME(SysTime sysTime) @safe;
}
else version(Windows)
{
    SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe
    {
        const max = SysTime.max;

        static void throwLaterThanMax()
        {
            throw new DateTimeException("The given SYSTEMTIME is for a date greater than SysTime.max.");
        }

        if(st.wYear > max.year)
            throwLaterThanMax();
        else if(st.wYear == max.year)
        {
            if(st.wMonth > max.month)
                throwLaterThanMax();
            else if(st.wMonth == max.month)
            {
                if(st.wDay > max.day)
                    throwLaterThanMax();
                else if(st.wDay == max.day)
                {
                    if(st.wHour > max.hour)
                        throwLaterThanMax();
                    else if(st.wHour == max.hour)
                    {
                        if(st.wMinute > max.minute)
                            throwLaterThanMax();
                        else if(st.wMinute == max.minute)
                        {
                            if(st.wSecond > max.second)
                                throwLaterThanMax();
                            else if(st.wSecond == max.second)
                            {
                                if(st.wMilliseconds > max.fracSecs.total!"msecs")
                                    throwLaterThanMax();
                            }
                        }
                    }
                }
            }
        }

        auto dt = DateTime(st.wYear, st.wMonth, st.wDay,
                           st.wHour, st.wMinute, st.wSecond);

        return SysTime(dt, msecs(st.wMilliseconds), tz);
    }

    unittest
    {
        auto sysTime = Clock.currTime(UTC());
        SYSTEMTIME st = void;
        GetSystemTime(&st);
        auto converted = SYSTEMTIMEToSysTime(&st, UTC());

        assert(abs((converted - sysTime)) <= dur!"seconds"(2));
    }


    SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe
    {
        immutable dt = cast(DateTime)sysTime;

        if(dt.year < 1601)
            throw new DateTimeException("SYSTEMTIME cannot hold dates prior to the year 1601.");

        SYSTEMTIME st;

        st.wYear = dt.year;
        st.wMonth = dt.month;
        st.wDayOfWeek = dt.dayOfWeek;
        st.wDay = dt.day;
        st.wHour = dt.hour;
        st.wMinute = dt.minute;
        st.wSecond = dt.second;
        st.wMilliseconds = cast(ushort)sysTime.fracSecs.total!"msecs";

        return st;
    }

    unittest
    {
        SYSTEMTIME st = void;
        GetSystemTime(&st);
        auto sysTime = SYSTEMTIMEToSysTime(&st, UTC());

        SYSTEMTIME result = SysTimeToSYSTEMTIME(sysTime);

        assert(st.wYear == result.wYear);
        assert(st.wMonth == result.wMonth);
        assert(st.wDayOfWeek == result.wDayOfWeek);
        assert(st.wDay == result.wDay);
        assert(st.wHour == result.wHour);
        assert(st.wMinute == result.wMinute);
        assert(st.wSecond == result.wSecond);
        assert(st.wMilliseconds == result.wMilliseconds);
    }

    private enum hnsecsFrom1601 = 504_911_232_000_000_000L;

    long FILETIMEToStdTime(const FILETIME* ft) @safe
    {
        ULARGE_INTEGER ul;
        ul.HighPart = ft.dwHighDateTime;
        ul.LowPart = ft.dwLowDateTime;
        ulong tempHNSecs = ul.QuadPart;

        if(tempHNSecs > long.max - hnsecsFrom1601)
            throw new DateTimeException("The given FILETIME cannot be represented as a stdTime value.");

        return cast(long)tempHNSecs + hnsecsFrom1601;
    }

    SysTime FILETIMEToSysTime(const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe
    {
        auto sysTime = SysTime(FILETIMEToStdTime(ft), UTC());
        sysTime.timezone = tz;

        return sysTime;
    }

    unittest
    {
        auto sysTime = Clock.currTime(UTC());
        SYSTEMTIME st = void;
        GetSystemTime(&st);

        FILETIME ft = void;
        SystemTimeToFileTime(&st, &ft);

        auto converted = FILETIMEToSysTime(&ft);

        assert(abs((converted - sysTime)) <= dur!"seconds"(2));
    }


    FILETIME stdTimeToFILETIME(long stdTime) @safe
    {
        if(stdTime < hnsecsFrom1601)
            throw new DateTimeException("The given stdTime value cannot be represented as a FILETIME.");

        ULARGE_INTEGER ul;
        ul.QuadPart = cast(ulong)stdTime - hnsecsFrom1601;

        FILETIME ft;
        ft.dwHighDateTime = ul.HighPart;
        ft.dwLowDateTime = ul.LowPart;

        return ft;
    }

    FILETIME SysTimeToFILETIME(SysTime sysTime) @safe
    {
        return stdTimeToFILETIME(sysTime.stdTime);
    }

    unittest
    {
        SYSTEMTIME st = void;
        GetSystemTime(&st);

        FILETIME ft = void;
        SystemTimeToFileTime(&st, &ft);
        auto sysTime = FILETIMEToSysTime(&ft, UTC());

        FILETIME result = SysTimeToFILETIME(sysTime);

        assert(ft.dwLowDateTime == result.dwLowDateTime);
        assert(ft.dwHighDateTime == result.dwHighDateTime);
    }
}


/++
    Type representing the DOS file date/time format.
  +/
alias DosFileTime = uint;

/++
    Converts from DOS file date/time to $(LREF SysTime).

    Params:
        dft = The DOS file time to convert.
        tz  = The time zone which the DOS file time is assumed to be in.

    Throws:
        $(LREF DateTimeException) if the $(D DosFileTime) is invalid.
  +/
SysTime DosFileTimeToSysTime(DosFileTime dft, immutable TimeZone tz = LocalTime()) @safe
{
    uint dt = cast(uint)dft;

    if(dt == 0)
        throw new DateTimeException("Invalid DosFileTime.");

    int year = ((dt >> 25) & 0x7F) + 1980;
    int month = ((dt >> 21) & 0x0F);       // 1..12
    int dayOfMonth = ((dt >> 16) & 0x1F);  // 1..31
    int hour = (dt >> 11) & 0x1F;          // 0..23
    int minute = (dt >> 5) & 0x3F;         // 0..59
    int second = (dt << 1) & 0x3E;         // 0..58 (in 2 second increments)

    try
        return SysTime(DateTime(year, month, dayOfMonth, hour, minute, second), tz);
    catch(DateTimeException dte)
        throw new DateTimeException("Invalid DosFileTime", __FILE__, __LINE__, dte);
}

unittest
{
    assert(DosFileTimeToSysTime(0b00000000001000010000000000000000) ==
                    SysTime(DateTime(1980, 1, 1, 0, 0, 0)));

    assert(DosFileTimeToSysTime(0b11111111100111111011111101111101) ==
                    SysTime(DateTime(2107, 12, 31, 23, 59, 58)));

    assert(DosFileTimeToSysTime(0x3E3F8456) ==
                    SysTime(DateTime(2011, 1, 31, 16, 34, 44)));
}


/++
    Converts from $(LREF SysTime) to DOS file date/time.

    Params:
        sysTime = The $(LREF SysTime) to convert.

    Throws:
        $(LREF DateTimeException) if the given $(LREF SysTime) cannot be converted to
        a $(D DosFileTime).
  +/
DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe
{
    auto dateTime = cast(DateTime)sysTime;

    if(dateTime.year < 1980)
        throw new DateTimeException("DOS File Times cannot hold dates prior to 1980.");

    if(dateTime.year > 2107)
        throw new DateTimeException("DOS File Times cannot hold dates past 2107.");

    uint retval = 0;
    retval = (dateTime.year - 1980) << 25;
    retval |= (dateTime.month & 0x0F) << 21;
    retval |= (dateTime.day & 0x1F) << 16;
    retval |= (dateTime.hour & 0x1F) << 11;
    retval |= (dateTime.minute & 0x3F) << 5;
    retval |= (dateTime.second >> 1) & 0x1F;

    return cast(DosFileTime)retval;
}

unittest
{
    assert(SysTimeToDosFileTime(SysTime(DateTime(1980, 1, 1, 0, 0, 0))) ==
                    0b00000000001000010000000000000000);

    assert(SysTimeToDosFileTime(SysTime(DateTime(2107, 12, 31, 23, 59, 58))) ==
                    0b11111111100111111011111101111101);

    assert(SysTimeToDosFileTime(SysTime(DateTime(2011, 1, 31, 16, 34, 44))) ==
                    0x3E3F8456);
}


/++
    The given array of $(D char) or random-access range of $(D char) or
    $(D ubyte) is expected to be in the format specified in
    $(WEB tools.ietf.org/html/rfc5322, RFC 5322) section 3.3 with the
    grammar rule $(I date-time). It is the date-time format commonly used in
    internet messages such as e-mail and HTTP. The corresponding
    $(LREF SysTime) will be returned.

    RFC 822 was the original spec (hence the function's name), whereas RFC 5322
    is the current spec.

    The day of the week is ignored beyond verifying that it's a valid day of the
    week, as the day of the week can be inferred from the date. It is not
    checked whether the given day of the week matches the actual day of the week
    of the given date (though it is technically invalid per the spec if the
    day of the week doesn't match the actual day of the week of the given date).

    If the time zone is $(D "-0000") (or considered to be equivalent to
    $(D "-0000") by section 4.3 of the spec), a $(LREF SimpleTimeZone) with a
    utc offset of $(D 0) is used rather than $(LREF UTC), whereas $(D "+0000")
    uses $(LREF UTC).

    Note that because $(LREF SysTime) does not currently support having a second
    value of 60 (as is sometimes done for leap seconds), if the date-time value
    does have a value of 60 for the seconds, it is treated as 59.

    The one area in which this function violates RFC 5322 is that it accepts
    $(D "\n") in folding whitespace in the place of $(D "\r\n"), because the
    HTTP spec requires it.

    Throws:
        $(LREF DateTimeException) if the given string doesn't follow the grammar
        for a date-time field or if the resulting $(LREF SysTime) is invalid.
  +/
SysTime parseRFC822DateTime()(in char[] value) @safe
{
    import std.string : representation;
    return parseRFC822DateTime(value.representation);
}

/++ Ditto +/
SysTime parseRFC822DateTime(R)(R value) @safe
    if(isRandomAccessRange!R && hasSlicing!R && hasLength!R &&
       (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte)))
{
    import std.functional : not;
    import std.ascii : isDigit, isAlpha, isPrintable;
    import std.typecons : Rebindable;
    import std.string : capitalize, format;
    import std.conv : to;
    import std.algorithm : find, all;

    void stripAndCheckLen(R valueBefore, size_t minLen, size_t line = __LINE__)
    {
        value = _stripCFWS(valueBefore);
        if(value.length < minLen)
            throw new DateTimeException("date-time value too short", __FILE__, line);
    }
    stripAndCheckLen(value, "7Dec1200:00A".length);

    static if(isArray!R && (is(ElementEncodingType!R == char) || is(ElementEncodingType!R == ubyte)))
    {
        static string sliceAsString(R str) @trusted
        {
            return cast(string)str;
        }
    }
    else
    {
        char[4] temp;
        char[] sliceAsString(R str) @trusted
        {
            size_t i = 0;
            foreach(c; str)
                temp[i++] = cast(char)c;
            return temp[0 .. str.length];
        }
    }

    // day-of-week
    if(isAlpha(value[0]))
    {
        auto dowStr = sliceAsString(value[0 .. 3]);
        switch(dowStr)
        {
            foreach(dow; EnumMembers!DayOfWeek)
            {
                enum dowC = capitalize(to!string(dow));
                case dowC:
                    goto afterDoW;
            }
            default: throw new DateTimeException(format("Invalid day-of-week: %s", dowStr));
        }
afterDoW: stripAndCheckLen(value[3 .. value.length], ",7Dec1200:00A".length);
        if(value[0] != ',')
            throw new DateTimeException("day-of-week missing comma");
        stripAndCheckLen(value[1 .. value.length], "7Dec1200:00A".length);
    }

    // day
    immutable digits = isDigit(value[1]) ? 2 : 1;
    immutable day = _convDigits!short(value[0 .. digits]);
    if(day == -1)
        throw new DateTimeException("Invalid day");
    stripAndCheckLen(value[digits .. value.length], "Dec1200:00A".length);

    // month
    Month month;
    {
        auto monStr = sliceAsString(value[0 .. 3]);
        switch(monStr)
        {
            foreach(mon; EnumMembers!Month)
            {
                enum monC = capitalize(to!string(mon));
                case monC:
                {
                    month = mon;
                    goto afterMon;
                }
            }
            default: throw new DateTimeException(format("Invalid month: %s", monStr));
        }
afterMon: stripAndCheckLen(value[3 .. value.length], "1200:00A".length);
    }

    // year
    auto found = value[2 .. value.length].find!(not!(std.ascii.isDigit))();
    size_t yearLen = value.length - found.length;
    if(found.length == 0)
        throw new DateTimeException("Invalid year");
    if(found[0] == ':')
        yearLen -= 2;
    auto year = _convDigits!short(value[0 .. yearLen]);
    if(year < 1900)
    {
        if(year == -1)
            throw new DateTimeException("Invalid year");
        if(yearLen < 4)
        {
            if(yearLen == 3)
                year += 1900;
            else if(yearLen == 2)
                year += year < 50 ? 2000 : 1900;
            else
                throw new DateTimeException("Invalid year. Too few digits.");
        }
        else
            throw new DateTimeException("Invalid year. Cannot be earlier than 1900.");
    }
    stripAndCheckLen(value[yearLen .. value.length], "00:00A".length);

    // hour
    immutable hour = _convDigits!short(value[0 .. 2]);
    stripAndCheckLen(value[2 .. value.length], ":00A".length);
    if(value[0] != ':')
        throw new DateTimeException("Invalid hour");
    stripAndCheckLen(value[1 .. value.length], "00A".length);

    // minute
    immutable minute = _convDigits!short(value[0 .. 2]);
    stripAndCheckLen(value[2 .. value.length], "A".length);

    // second
    short second;
    if(value[0] == ':')
    {
        stripAndCheckLen(value[1 .. value.length], "00A".length);
        second = _convDigits!short(value[0 .. 2]);
        // this is just if/until SysTime is sorted out to fully support leap seconds
        if(second == 60)
            second = 59;
        stripAndCheckLen(value[2 .. value.length], "A".length);
    }

    immutable(TimeZone) parseTZ(int sign)
    {
        if(value.length < 5)
            throw new DateTimeException("Invalid timezone");
        immutable zoneHours = _convDigits!short(value[1 .. 3]);
        immutable zoneMinutes = _convDigits!short(value[3 .. 5]);
        if(zoneHours == -1 || zoneMinutes == -1 || zoneMinutes > 59)
            throw new DateTimeException("Invalid timezone");
        value = value[5 .. value.length];
        immutable utcOffset = (dur!"hours"(zoneHours) + dur!"minutes"(zoneMinutes)) * sign;
        if(utcOffset == Duration.zero)
        {
            return sign == 1 ? cast(immutable(TimeZone))UTC()
                             : cast(immutable(TimeZone))new immutable SimpleTimeZone(Duration.zero);
        }
        return new immutable(SimpleTimeZone)(utcOffset);
    }

    // zone
    Rebindable!(immutable TimeZone) tz;
    if(value[0] == '-')
        tz = parseTZ(-1);
    else if(value[0] == '+')
        tz = parseTZ(1);
    else
    {
        // obs-zone
        immutable tzLen = value.length - find(value, ' ', '\t', '(')[0].length;
        switch(sliceAsString(value[0 .. tzLen <= 4 ? tzLen : 4]))
        {
            case "UT": case "GMT": tz = UTC(); break;
            case "EST": tz = new immutable SimpleTimeZone(dur!"hours"(-5)); break;
            case "EDT": tz = new immutable SimpleTimeZone(dur!"hours"(-4)); break;
            case "CST": tz = new immutable SimpleTimeZone(dur!"hours"(-6)); break;
            case "CDT": tz = new immutable SimpleTimeZone(dur!"hours"(-5)); break;
            case "MST": tz = new immutable SimpleTimeZone(dur!"hours"(-7)); break;
            case "MDT": tz = new immutable SimpleTimeZone(dur!"hours"(-6)); break;
            case "PST": tz = new immutable SimpleTimeZone(dur!"hours"(-8)); break;
            case "PDT": tz = new immutable SimpleTimeZone(dur!"hours"(-7)); break;
            case "J": case "j": throw new DateTimeException("Invalid timezone");
            default:
            {
                if(all!(std.ascii.isAlpha)(value[0 .. tzLen]))
                {
                    tz = new immutable SimpleTimeZone(Duration.zero);
                    break;
                }
                throw new DateTimeException("Invalid timezone");
            }
        }
        value = value[tzLen .. value.length];
    }

    // This is kind of arbitrary. Technically, nothing but CFWS is legal past
    // the end of the timezone, but we don't want to be picky about that in a
    // function that's just parsing rather than validating. So, the idea here is
    // that if the next character is printable (and not part of CFWS), then it
    // might be part of the timezone and thus affect what the timezone was
    // supposed to be, so we'll throw, but otherwise, we'll just ignore it.
    if(!value.empty && isPrintable(value[0]) && value[0] != ' ' && value[0] != '(')
        throw new DateTimeException("Invalid timezone");

    try
        return SysTime(DateTime(year, month, day, hour, minute, second), tz);
    catch(DateTimeException dte)
        throw new DateTimeException("date-time format is correct, but the resulting SysTime is invalid.", dte);
}

///
unittest
{
    auto tz = new immutable SimpleTimeZone(hours(-8));
    assert(parseRFC822DateTime("Sat, 6 Jan 1990 12:14:19 -0800") ==
           SysTime(DateTime(1990, 1, 6, 12, 14, 19), tz));

    assert(parseRFC822DateTime("9 Jul 2002 13:11 +0000") ==
           SysTime(DateTime(2002, 7, 9, 13, 11, 0), UTC()));

    auto badStr = "29 Feb 2001 12:17:16 +0200";
    assertThrown!DateTimeException(parseRFC822DateTime(badStr));
}

version(unittest) void testParse822(alias cr)(string str, SysTime expected, size_t line = __LINE__)
{
    import std.string;
    import std.format : format;
    auto value = cr(str);
    auto result = parseRFC822DateTime(value);
    if(result != expected)
        throw new AssertError(format("wrong result. expected [%s], actual[%s]", expected, result), __FILE__, line);
}

version(unittest) void testBadParse822(alias cr)(string str, size_t line = __LINE__)
{
    try
        parseRFC822DateTime(cr(str));
    catch(DateTimeException)
        return;
    throw new AssertError("No DateTimeException was thrown", __FILE__, line);
}

unittest
{
    import std.algorithm;
    import std.ascii;
    import std.format : format;
    import std.meta;
    import std.range;
    import std.string;
    import std.typecons;

    static struct Rand3Letters
    {
        enum empty = false;
        @property auto front() { return _mon; }
        void popFront()
        {
            import std.random;
            _mon = rndGen.map!(a => letters[a % letters.length])().take(3).array().assumeUnique();
        }
        string _mon;
        static auto start() { Rand3Letters retval; retval.popFront(); return retval; }
    }

    foreach(cr; AliasSeq!(function(string a){return cast(char[])a;},
                          function(string a){return cast(ubyte[])a;},
                          function(string a){return a;},
                          function(string a){return map!(b => cast(char)b)(a.representation);}))
    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
        scope(failure) writeln(typeof(cr).stringof);
        alias test = testParse822!cr;
        alias testBad = testBadParse822!cr;

        immutable std1 = DateTime(2012, 12, 21, 13, 14, 15);
        immutable std2 = DateTime(2012, 12, 21, 13, 14, 0);
        immutable dst1 = DateTime(1976, 7, 4, 5, 4, 22);
        immutable dst2 = DateTime(1976, 7, 4, 5, 4, 0);

        test("21 Dec 2012 13:14:15 +0000", SysTime(std1, UTC()));
        test("21 Dec 2012 13:14 +0000", SysTime(std2, UTC()));
        test("Fri, 21 Dec 2012 13:14 +0000", SysTime(std2, UTC()));
        test("Fri, 21 Dec 2012 13:14:15 +0000", SysTime(std1, UTC()));

        test("04 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));
        test("04 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
        test("Sun, 04 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
        test("Sun, 04 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));

        test("4 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));
        test("4 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
        test("Sun, 4 Jul 1976 05:04 +0000", SysTime(dst2, UTC()));
        test("Sun, 4 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC()));

        auto badTZ = new immutable SimpleTimeZone(Duration.zero);
        test("21 Dec 2012 13:14:15 -0000", SysTime(std1, badTZ));
        test("21 Dec 2012 13:14 -0000", SysTime(std2, badTZ));
        test("Fri, 21 Dec 2012 13:14 -0000", SysTime(std2, badTZ));
        test("Fri, 21 Dec 2012 13:14:15 -0000", SysTime(std1, badTZ));

        test("04 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));
        test("04 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
        test("Sun, 04 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
        test("Sun, 04 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));

        test("4 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));
        test("4 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
        test("Sun, 4 Jul 1976 05:04 -0000", SysTime(dst2, badTZ));
        test("Sun, 4 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ));

        auto pst = new immutable SimpleTimeZone(dur!"hours"(-8));
        auto pdt = new immutable SimpleTimeZone(dur!"hours"(-7));
        test("21 Dec 2012 13:14:15 -0800", SysTime(std1, pst));
        test("21 Dec 2012 13:14 -0800", SysTime(std2, pst));
        test("Fri, 21 Dec 2012 13:14 -0800", SysTime(std2, pst));
        test("Fri, 21 Dec 2012 13:14:15 -0800", SysTime(std1, pst));

        test("04 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));
        test("04 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
        test("Sun, 04 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
        test("Sun, 04 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));

        test("4 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));
        test("4 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
        test("Sun, 4 Jul 1976 05:04 -0700", SysTime(dst2, pdt));
        test("Sun, 4 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt));

        auto cet = new immutable SimpleTimeZone(dur!"hours"(1));
        auto cest = new immutable SimpleTimeZone(dur!"hours"(2));
        test("21 Dec 2012 13:14:15 +0100", SysTime(std1, cet));
        test("21 Dec 2012 13:14 +0100", SysTime(std2, cet));
        test("Fri, 21 Dec 2012 13:14 +0100", SysTime(std2, cet));
        test("Fri, 21 Dec 2012 13:14:15 +0100", SysTime(std1, cet));

        test("04 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));
        test("04 Jul 1976 05:04 +0200", SysTime(dst2, cest));
        test("Sun, 04 Jul 1976 05:04 +0200", SysTime(dst2, cest));
        test("Sun, 04 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));

        test("4 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));
        test("4 Jul 1976 05:04 +0200", SysTime(dst2, cest));
        test("Sun, 4 Jul 1976 05:04 +0200", SysTime(dst2, cest));
        test("Sun, 4 Jul 1976 05:04:22 +0200", SysTime(dst1, cest));

        // dst and std times are switched in the Southern Hemisphere which is why the
        // time zone names and DateTime variables don't match.
        auto cstStd = new immutable SimpleTimeZone(dur!"hours"(9) + dur!"minutes"(30));
        auto cstDST = new immutable SimpleTimeZone(dur!"hours"(10) + dur!"minutes"(30));
        test("21 Dec 2012 13:14:15 +1030", SysTime(std1, cstDST));
        test("21 Dec 2012 13:14 +1030", SysTime(std2, cstDST));
        test("Fri, 21 Dec 2012 13:14 +1030", SysTime(std2, cstDST));
        test("Fri, 21 Dec 2012 13:14:15 +1030", SysTime(std1, cstDST));

        test("04 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
        test("04 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
        test("Sun, 04 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
        test("Sun, 04 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));

        test("4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
        test("4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
        test("Sun, 4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
        test("Sun, 4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));

        foreach(int i, mon; _monthNames)
        {
            test(format("17 %s 2012 00:05:02 +0000", mon), SysTime(DateTime(2012, i + 1, 17, 0, 5, 2), UTC()));
            test(format("17 %s 2012 00:05 +0000", mon), SysTime(DateTime(2012, i + 1, 17, 0, 5, 0), UTC()));
        }

        import std.uni;
        foreach(mon; chain(_monthNames[].map!(a => toLower(a))(),
                           _monthNames[].map!(a => toUpper(a))(),
                           ["Jam", "Jen", "Fec", "Fdb", "Mas", "Mbr", "Aps", "Aqr", "Mai", "Miy",
                            "Jum", "Jbn", "Jup", "Jal", "Aur", "Apg", "Sem", "Sap", "Ocm", "Odt",
                            "Nom", "Nav", "Dem", "Dac"],
                           Rand3Letters.start().take(20)))
        {
            scope(failure) writefln("Month: %s", mon);
            testBad(format("17 %s 2012 00:05:02 +0000", mon));
            testBad(format("17 %s 2012 00:05 +0000", mon));
        }

        immutable string[7] daysOfWeekNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

        {
            auto start = SysTime(DateTime(2012, 11, 11, 9, 42, 0), UTC());
            int day = 11;

            foreach(int i, dow; daysOfWeekNames)
            {
                auto curr = start + dur!"days"(i);
                test(format("%s, %s Nov 2012 09:42:00 +0000", dow, day), curr);
                test(format("%s, %s Nov 2012 09:42 +0000", dow, day++), curr);

                // Whether the day of the week matches the date is ignored.
                test(format("%s, 11 Nov 2012 09:42:00 +0000", dow), start);
                test(format("%s, 11 Nov 2012 09:42 +0000", dow), start);
            }
        }

        foreach(dow; chain(daysOfWeekNames[].map!(a => toLower(a))(),
                           daysOfWeekNames[].map!(a => toUpper(a))(),
                           ["Sum", "Spn", "Mom", "Man", "Tuf", "Tae", "Wem", "Wdd", "The", "Tur",
                            "Fro", "Fai", "San", "Sut"],
                           Rand3Letters.start().take(20)))
        {
            scope(failure) writefln("Day of Week: %s", dow);
            testBad(format("%s, 11 Nov 2012 09:42:00 +0000", dow));
            testBad(format("%s, 11 Nov 2012 09:42 +0000", dow));
        }

        testBad("31 Dec 1899 23:59:59 +0000");
        test("01 Jan 1900 00:00:00 +0000", SysTime(Date(1900, 1, 1), UTC()));
        test("01 Jan 1900 00:00:00 -0000", SysTime(Date(1900, 1, 1),
                                                   new immutable SimpleTimeZone(Duration.zero)));
        test("01 Jan 1900 00:00:00 -0700", SysTime(Date(1900, 1, 1),
                                                   new immutable SimpleTimeZone(dur!"hours"(-7))));

        {
            auto st1 = SysTime(Date(1900, 1, 1), UTC());
            auto st2 = SysTime(Date(1900, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-11)));
            foreach(i; 1900 .. 2102)
            {
                test(format("1 Jan %05d 00:00 +0000", i), st1);
                test(format("1 Jan %05d 00:00 -1100", i), st2);
                st1.add!"years"(1);
                st2.add!"years"(1);
            }
            st1.year = 9998;
            st2.year = 9998;
            foreach(i; 9998 .. 11_002)
            {
                test(format("1 Jan %05d 00:00 +0000", i), st1);
                test(format("1 Jan %05d 00:00 -1100", i), st2);
                st1.add!"years"(1);
                st2.add!"years"(1);
            }
        }

        testBad("12 Feb 1907 23:17:09 0000");
        testBad("12 Feb 1907 23:17:09 +000");
        testBad("12 Feb 1907 23:17:09 -000");
        testBad("12 Feb 1907 23:17:09 +00000");
        testBad("12 Feb 1907 23:17:09 -00000");
        testBad("12 Feb 1907 23:17:09 +A");
        testBad("12 Feb 1907 23:17:09 +PST");
        testBad("12 Feb 1907 23:17:09 -A");
        testBad("12 Feb 1907 23:17:09 -PST");

        // test trailing stuff that gets ignored
        {
            foreach(c; chain(iota(0, 33), ['('], iota(127, ubyte.max + 1)))
            {
                scope(failure) writefln("c: %d", c);
                test(format("21 Dec 2012 13:14:15 +0000%c", cast(char)c), SysTime(std1, UTC()));
                test(format("21 Dec 2012 13:14:15 +0000%c  ", cast(char)c), SysTime(std1, UTC()));
                test(format("21 Dec 2012 13:14:15 +0000%chello", cast(char)c), SysTime(std1, UTC()));
            }
        }

        // test trailing stuff that doesn't get ignored
        {
            foreach(c; chain(iota(33, '('), iota('(' + 1, 127)))
            {
                scope(failure) writefln("c: %d", c);
                testBad(format("21 Dec 2012 13:14:15 +0000%c", cast(char)c));
                testBad(format("21 Dec 2012 13:14:15 +0000%c   ", cast(char)c));
                testBad(format("21 Dec 2012 13:14:15 +0000%chello", cast(char)c));
            }
        }

        testBad("32 Jan 2012 12:13:14 -0800");
        testBad("31 Jan 2012 24:13:14 -0800");
        testBad("31 Jan 2012 12:60:14 -0800");
        testBad("31 Jan 2012 12:13:61 -0800");
        testBad("31 Jan 2012 12:13:14 -0860");
        test("31 Jan 2012 12:13:14 -0859",
             SysTime(DateTime(2012, 1, 31, 12, 13, 14),
                     new immutable SimpleTimeZone(dur!"hours"(-8) + dur!"minutes"(-59))));

        // leap-seconds
        test("21 Dec 2012 15:59:60 -0800", SysTime(DateTime(2012, 12, 21, 15, 59, 59), pst));

        // FWS
        test("Sun,4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd));
        test("Sun,4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd));
        test("Sun,4 Jul 1976 05:04 +0930 (foo)", SysTime(dst2, cstStd));
        test("Sun,4 Jul 1976 05:04:22 +0930 (foo)", SysTime(dst1, cstStd));
        test("Sun,4  \r\n  Jul  \r\n  1976  \r\n  05:04  \r\n  +0930  \r\n  (foo)", SysTime(dst2, cstStd));
        test("Sun,4  \r\n  Jul  \r\n  1976  \r\n  05:04:22  \r\n  +0930  \r\n  (foo)", SysTime(dst1, cstStd));

        auto str = "01 Jan 2012 12:13:14 -0800 ";
        test(str, SysTime(DateTime(2012, 1, 1, 12, 13, 14), new immutable SimpleTimeZone(hours(-8))));
        foreach(i; 0 .. str.length)
        {
            auto currStr = str.dup;
            currStr[i] = 'x';
            scope(failure) writefln("failed: %s", currStr);
            testBad(cast(string)currStr);
        }
        foreach(i; 2 .. str.length)
        {
            auto currStr = str[0 .. $ - i];
            scope(failure) writefln("failed: %s", currStr);
            testBad(cast(string)currStr);
            testBad((cast(string)currStr) ~ "                                    ");
        }
    }();
}

// Obsolete Format per section 4.3 of RFC 5322.
unittest
{
    import std.algorithm;
    import std.ascii;
    import std.format : format;
    import std.meta;
    import std.range;
    import std.string;
    import std.typecons;

    auto std1 = SysTime(DateTime(2012, 12, 21, 13, 14, 15), UTC());
    auto std2 = SysTime(DateTime(2012, 12, 21, 13, 14, 0), UTC());
    auto std3 = SysTime(DateTime(1912, 12, 21, 13, 14, 15), UTC());
    auto std4 = SysTime(DateTime(1912, 12, 21, 13, 14, 0), UTC());
    auto dst1 = SysTime(DateTime(1976, 7, 4, 5, 4, 22), UTC());
    auto dst2 = SysTime(DateTime(1976, 7, 4, 5, 4, 0), UTC());
    auto tooLate1 = SysTime(Date(10_000, 1, 1), UTC());
    auto tooLate2 = SysTime(DateTime(12_007, 12, 31, 12, 22, 19), UTC());

    foreach(cr; AliasSeq!(function(string a){return cast(char[])a;},
                          function(string a){return cast(ubyte[])a;},
                          function(string a){return a;},
                          function(string a){return map!(b => cast(char)b)(a.representation);}))
    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
        scope(failure) writeln(typeof(cr).stringof);
        alias test = testParse822!cr;
        {
            auto list = ["", " ", " \r\n\t", "\t\r\n (hello world( frien(dog)) silly \r\n )  \t\t \r\n ()",
                         " \n ", "\t\n\t", " \n\t (foo) \n (bar) \r\n (baz) \n "];

            foreach(i, cfws; list)
            {
                scope(failure) writefln("i: %s", i);

                test(format("%1$s21%1$sDec%1$s2012%1$s13:14:15%1$s+0000%1$s", cfws), std1);
                test(format("%1$s21%1$sDec%1$s2012%1$s13:14%1$s+0000%1$s", cfws), std2);
                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s2012%1$s13:14%1$s+0000%1$s", cfws), std2);
                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s2012%1$s13:14:15%1$s+0000%1$s", cfws), std1);

                test(format("%1$s04%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
                test(format("%1$s04%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s1976%1$s05:04:22 +0000%1$s", cfws), dst1);

                test(format("%1$s4%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
                test(format("%1$s4%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1);

                test(format("%1$s21%1$sDec%1$s12%1$s13:14:15%1$s+0000%1$s", cfws), std1);
                test(format("%1$s21%1$sDec%1$s12%1$s13:14%1$s+0000%1$s", cfws), std2);
                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s12%1$s13:14%1$s+0000%1$s", cfws), std2);
                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s12%1$s13:14:15%1$s+0000%1$s", cfws), std1);

                test(format("%1$s04%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
                test(format("%1$s04%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1);

                test(format("%1$s4%1$sJul%1$s76 05:04:22%1$s+0000%1$s", cfws), dst1);
                test(format("%1$s4%1$sJul%1$s76 05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1);

                test(format("%1$s21%1$sDec%1$s012%1$s13:14:15%1$s+0000%1$s", cfws), std3);
                test(format("%1$s21%1$sDec%1$s012%1$s13:14%1$s+0000%1$s", cfws), std4);
                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s012%1$s13:14%1$s+0000%1$s", cfws), std4);
                test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s012%1$s13:14:15%1$s+0000%1$s", cfws), std3);

                test(format("%1$s04%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
                test(format("%1$s04%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);

                test(format("%1$s4%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);
                test(format("%1$s4%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2);
                test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1);

                test(format("%1$s1%1$sJan%1$s10000%1$s00:00:00%1$s+0000%1$s", cfws), tooLate1);
                test(format("%1$s31%1$sDec%1$s12007%1$s12:22:19%1$s+0000%1$s", cfws), tooLate2);
                test(format("%1$sSat%1$s,%1$s1%1$sJan%1$s10000%1$s00:00:00%1$s+0000%1$s", cfws), tooLate1);
                test(format("%1$sSun%1$s,%1$s31%1$sDec%1$s12007%1$s12:22:19%1$s+0000%1$s", cfws), tooLate2);
            }
        }

        // test years of 1, 2, and 3 digits.
        {
            auto st1 = SysTime(Date(2000, 1, 1), UTC());
            auto st2 = SysTime(Date(2000, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-12)));
            foreach(i; 0 .. 50)
            {
                test(format("1 Jan %02d 00:00 GMT", i), st1);
                test(format("1 Jan %02d 00:00 -1200", i), st2);
                st1.add!"years"(1);
                st2.add!"years"(1);
            }
        }

        {
            auto st1 = SysTime(Date(1950, 1, 1), UTC());
            auto st2 = SysTime(Date(1950, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-12)));
            foreach(i; 50 .. 100)
            {
                test(format("1 Jan %02d 00:00 GMT", i), st1);
                test(format("1 Jan %02d 00:00 -1200", i), st2);
                st1.add!"years"(1);
                st2.add!"years"(1);
            }
        }

        {
            auto st1 = SysTime(Date(1900, 1, 1), UTC());
            auto st2 = SysTime(Date(1900, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-11)));
            foreach(i; 0 .. 1000)
            {
                test(format("1 Jan %03d 00:00 GMT", i), st1);
                test(format("1 Jan %03d 00:00 -1100", i), st2);
                st1.add!"years"(1);
                st2.add!"years"(1);
            }
        }

        foreach(i; 0 .. 10)
        {
            auto str1 = cr(format("1 Jan %d 00:00 GMT", i));
            auto str2 = cr(format("1 Jan %d 00:00 -1200", i));
            assertThrown!DateTimeException(parseRFC822DateTime(str1));
            assertThrown!DateTimeException(parseRFC822DateTime(str1));
        }

        // test time zones
        {
            auto dt = DateTime(1982, 05, 03, 12, 22, 04);
            test("Wed, 03 May 1982 12:22:04 UT", SysTime(dt, UTC()));
            test("Wed, 03 May 1982 12:22:04 GMT", SysTime(dt, UTC()));
            test("Wed, 03 May 1982 12:22:04 EST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-5))));
            test("Wed, 03 May 1982 12:22:04 EDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-4))));
            test("Wed, 03 May 1982 12:22:04 CST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-6))));
            test("Wed, 03 May 1982 12:22:04 CDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-5))));
            test("Wed, 03 May 1982 12:22:04 MST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-7))));
            test("Wed, 03 May 1982 12:22:04 MDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-6))));
            test("Wed, 03 May 1982 12:22:04 PST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-8))));
            test("Wed, 03 May 1982 12:22:04 PDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-7))));

            auto badTZ = new immutable SimpleTimeZone(Duration.zero);
            foreach(dchar c; filter!(a => a != 'j' && a != 'J')(letters))
            {
                scope(failure) writefln("c: %s", c);
                test(format("Wed, 03 May 1982 12:22:04 %s", c), SysTime(dt, badTZ));
                test(format("Wed, 03 May 1982 12:22:04%s", c), SysTime(dt, badTZ));
            }

            foreach(dchar c; ['j', 'J'])
            {
                scope(failure) writefln("c: %s", c);
                assertThrown!DateTimeException(parseRFC822DateTime(cr(format("Wed, 03 May 1982 12:22:04 %s", c))));
                assertThrown!DateTimeException(parseRFC822DateTime(cr(format("Wed, 03 May 1982 12:22:04%s", c))));
            }

            foreach(string s; ["AAA", "GQW", "DDT", "PDA", "GT", "GM"])
            {
                scope(failure) writefln("s: %s", s);
                test(format("Wed, 03 May 1982 12:22:04 %s", s), SysTime(dt, badTZ));
            }

            // test trailing stuff that gets ignored
            {
                foreach(c; chain(iota(0, 33), ['('], iota(127, ubyte.max + 1)))
                {
                    scope(failure) writefln("c: %d", c);
                    test(format("21Dec1213:14:15+0000%c", cast(char)c), std1);
                    test(format("21Dec1213:14:15+0000%c  ", cast(char)c), std1);
                    test(format("21Dec1213:14:15+0000%chello", cast(char)c), std1);
                }
            }

            // test trailing stuff that doesn't get ignored
            {
                foreach(c; chain(iota(33, '('), iota('(' + 1, 127)))
                {
                    scope(failure) writefln("c: %d", c);
                    assertThrown!DateTimeException(parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%c", cast(char)c))));
                    assertThrown!DateTimeException(parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%c  ", cast(char)c))));
                    assertThrown!DateTimeException(parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%chello", cast(char)c))));
                }
            }
        }

        // test that the checks for minimum length work correctly and avoid
        // any RangeErrors.
        test("7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
                                     new immutable SimpleTimeZone(Duration.zero)));
        test("Fri,7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
                                         new immutable SimpleTimeZone(Duration.zero)));
        test("7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
                                        new immutable SimpleTimeZone(Duration.zero)));
        test("Fri,7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00),
                                            new immutable SimpleTimeZone(Duration.zero)));

        auto tooShortMsg = collectExceptionMsg!DateTimeException(parseRFC822DateTime(""));
        foreach(str; ["Fri,7Dec1200:00:00", "7Dec1200:00:00"])
        {
            foreach(i; 0 .. str.length)
            {
                auto value = str[0 .. $ - i];
                scope(failure) writeln(value);
                assert(collectExceptionMsg!DateTimeException(parseRFC822DateTime(value)) == tooShortMsg);
            }
        }
    }();
}


/++
    Whether all of the given strings are valid units of time.

    $(D "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
{
    import std.algorithm : canFind;
    foreach(str; units)
    {
        if(!canFind(timeStrings[], str))
            return false;
    }

    return true;
}


/++
    Compares two time unit strings. $(D "years") are the largest units and
    $(D "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.format : format;
    import std.algorithm : countUntil;

    auto tstrings = timeStrings;
    immutable indexOfLHS = countUntil(tstrings, lhs);
    immutable indexOfRHS = countUntil(tstrings, rhs);

    enforce(indexOfLHS != -1, format("%s is not a valid TimeString", lhs));
    enforce(indexOfRHS != -1, format("%s is not a valid TimeString", rhs));

    if(indexOfLHS < indexOfRHS)
        return -1;
    if(indexOfLHS > indexOfRHS)
        return 1;

    return 0;
}

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. $(D "years") are the largest
    units and $(D "hnsecs") are the smallest.

    This template is used instead of $(D cmpTimeUnits) because exceptions
    can't be thrown at compile time and $(D 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);
}


/+
    Helper function for $(D CmpTimeUnits).
 +/
private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow
{
    import std.algorithm : 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;
}

unittest
{
    import std.format : format;
    import std.meta;
    import std.string;
    import std.typecons;

    static string genTest(size_t index)
    {
        auto currUnits = timeStrings[index];
        auto test = format(`assert(CmpTimeUnits!("%s", "%s") == 0);`, currUnits, currUnits);

        foreach(units; timeStrings[index + 1 .. $])
            test ~= format(`assert(CmpTimeUnits!("%s", "%s") == -1);`, currUnits, units);

        foreach(units; timeStrings[0 .. index])
            test ~= format(`assert(CmpTimeUnits!("%s", "%s") == 1);`, currUnits, units);

        return test;
    }

    static assert(timeStrings.length == 10);
    foreach(n; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
        mixin(genTest(n));
}


/++
    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
    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 <= TimeOfDay.maxHour;
    else static if(units == "minutes")
        return value >= 0 && value <= TimeOfDay.maxMinute;
    else static if(units == "seconds")
        return value >= 0 && value <= TimeOfDay.maxSecond;
}

///
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.
        day   = The day to validate.
  +/
bool valid(string units)(int year, int month, int day) @safe pure nothrow
    if(units == "days")
{
    return day > 0 && day <= maxDay(year, month);
}


/++
    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 $(D 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);
    }
}


/++
    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);
}


/++
    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.
  +/
static 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;
}

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);
}


/++
    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.
  +/
static int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow
{
    if(currDoW == dow)
        return 0;

    if(currDoW < dow)
        return dow - currDoW;

    return (DayOfWeek.sat - currDoW) + dow + 1;
}

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);
}


version(StdDdoc)
{
    /++
        Function for starting to a stop watch time when the function is called
        and stopping it when its return value goes out of scope and is destroyed.

        When the value that is returned by this function is destroyed,
        $(D func) will run. $(D func) is a unary function that takes a
        $(CXREF time, TickDuration).

        Example:
--------------------
{
    auto mt = measureTime!((TickDuration a)
        { /+ do something when the scope is exited +/ });
    // do something that needs to be timed
}
--------------------

        which is functionally equivalent to

--------------------
{
    auto sw = StopWatch(AutoStart.yes);
    scope(exit)
    {
        TickDuration a = sw.peek();
        /+ do something when the scope is exited +/
    }
    // do something that needs to be timed
}
--------------------

        See_Also:
            $(LREF benchmark)
      +/
    auto measureTime(alias func)();
}
else
{
    @safe auto measureTime(alias func)()
        if(isSafe!((){StopWatch sw; unaryFun!func(sw.peek());}))
    {
        struct Result
        {
            private StopWatch _sw = void;
            this(AutoStart as)
            {
                _sw = StopWatch(as);
            }
            ~this()
            {
                unaryFun!(func)(_sw.peek());
            }
        }
        return Result(AutoStart.yes);
    }

    auto measureTime(alias func)()
        if(!isSafe!((){StopWatch sw; unaryFun!func(sw.peek());}))
    {
        struct Result
        {
            private StopWatch _sw = void;
            this(AutoStart as)
            {
                _sw = StopWatch(as);
            }
            ~this()
            {
                unaryFun!(func)(_sw.peek());
            }
        }
        return Result(AutoStart.yes);
    }
}

// Verify Example.
unittest
{
    {
        auto mt = measureTime!((TickDuration a)
            { /+ do something when the scope is exited +/ });
        // do something that needs to be timed
    }

    {
        auto sw = StopWatch(AutoStart.yes);
        scope(exit)
        {
            TickDuration a = sw.peek();
            /+ do something when the scope is exited +/
        }
        // do something that needs to be timed
    }
}

@safe unittest
{
    import std.math : isNaN;

    @safe static void func(TickDuration td)
    {
        assert(!td.to!("seconds", real)().isNaN());
    }

    auto mt = measureTime!(func)();

    /+
    with (measureTime!((a){assert(a.seconds);}))
    {
        // doSomething();
        // @@@BUG@@@ doesn't work yet.
    }
    +/
}

unittest
{
    import std.math : isNaN;

    static void func(TickDuration td)
    {
        assert(!td.to!("seconds", real)().isNaN());
    }

    auto mt = measureTime!(func)();

    /+
    with (measureTime!((a){assert(a.seconds);}))
    {
        // doSomething();
        // @@@BUG@@@ doesn't work yet.
    }
    +/
}

//Bug# 8450
unittest
{
    @safe    void safeFunc() {}
    @trusted void trustFunc() {}
    @system  void sysFunc() {}
    auto safeResult  = measureTime!((a){safeFunc();})();
    auto trustResult = measureTime!((a){trustFunc();})();
    auto sysResult   = measureTime!((a){sysFunc();})();
}

//==============================================================================
// Private Section.
//==============================================================================
private:

//==============================================================================
// Section with private enums and constants.
//==============================================================================

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];

/+
    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"];


//==============================================================================
// Section with private helper functions and templates.
//==============================================================================

/+
    Template to help with converting between time units.
 +/
template hnsecsPer(string units)
    if(CmpTimeUnits!(units, "months") < 0)
{
    static if(units == "hnsecs")
        enum hnsecsPer = 1L;
    else static if(units == "usecs")
        enum hnsecsPer = 10L;
    else static if(units == "msecs")
        enum hnsecsPer = 1000 * hnsecsPer!"usecs";
    else static if(units == "seconds")
        enum hnsecsPer = 1000 * hnsecsPer!"msecs";
    else static if(units == "minutes")
        enum hnsecsPer = 60 * hnsecsPer!"seconds";
    else static if(units == "hours")
        enum hnsecsPer = 60 * hnsecsPer!"minutes";
    else static if(units == "days")
        enum hnsecsPer = 24 * hnsecsPer!"hours";
    else static if(units == "weeks")
        enum hnsecsPer = 7 * hnsecsPer!"days";
}


/+
    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
    if(validTimeUnits(units) &&
       CmpTimeUnits!(units, "months") < 0)
{
    immutable value = convert!("hnsecs", units)(hnsecs);
    hnsecs -= convert!(units, "hnsecs")(value);

    return value;
}

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);
}


/+
    This function is used to split out the units without getting the remaining
    hnsecs.

    See_Also:
        $(LREF splitUnitsFromHNSecs)

    Params:
        units  = The units to split out.
        hnsecs = The current total hnsecs.

    Returns:
        The split out value.
  +/
long getUnitsFromHNSecs(string units)(long hnsecs) @safe pure nothrow
    if(validTimeUnits(units) &&
       CmpTimeUnits!(units, "months") < 0)
{
    return convert!("hnsecs", units)(hnsecs);
}

unittest
{
    auto hnsecs = 2595000000007L;
    immutable days = getUnitsFromHNSecs!"days"(hnsecs);
    assert(days == 3);
    assert(hnsecs == 2595000000007L);
}


/+
    This function is used to split out the units without getting the units but
    just the remaining hnsecs.

    See_Also:
        $(LREF splitUnitsFromHNSecs)

    Params:
        units  = The units to split out.
        hnsecs = The current total hnsecs.

    Returns:
        The remaining hnsecs.
  +/
long removeUnitsFromHNSecs(string units)(long hnsecs) @safe pure nothrow
    if(validTimeUnits(units) &&
       CmpTimeUnits!(units, "months") < 0)
{
    immutable value = convert!("hnsecs", units)(hnsecs);

    return hnsecs - convert!(units, "hnsecs")(value);
}

unittest
{
    auto hnsecs = 2595000000007L;
    auto returned = removeUnitsFromHNSecs!"days"(hnsecs);
    assert(returned == 3000000007);
    assert(hnsecs == 2595000000007L);
}


/+
    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.
 +/
static ubyte maxDay(int year, int month) @safe pure nothrow
in
{
    assert(valid!"months"(month));
}
body
{
    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.");
    }
}

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);
}


/+
    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
{
    //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;
    }
}

unittest
{
    //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);
}


/+
    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];
}

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:
        $(LREF DateTimeException) if the given month is not a valid month string.
  +/
Month monthFromString(string monthStr) @safe pure
{
    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));
    }
}

unittest
{
    foreach(badStr; ["Ja", "Janu", "Januar", "Januarys", "JJanuary", "JANUARY",
                     "JAN", "january", "jaNuary", "jaN", "jaNuaRy", "jAn"])
    {
        scope(failure) writeln(badStr);
        assertThrown!DateTimeException(monthFromString(badStr));
    }

    foreach(month; EnumMembers!Month)
    {
        scope(failure) writeln(month);
        assert(monthFromString(monthToString(month)) == month);
    }
}


/+
    The time units which are one step smaller than the given units.
  +/
template nextSmallerTimeUnits(string units)
    if(validTimeUnits(units) &&
       timeStrings.front != units)
{
    import std.algorithm : countUntil;
    enum nextSmallerTimeUnits = timeStrings[countUntil(timeStrings, units) - 1];
}

unittest
{
    assert(nextSmallerTimeUnits!"years" == "months");
    assert(nextSmallerTimeUnits!"months" == "weeks");
    assert(nextSmallerTimeUnits!"weeks" == "days");
    assert(nextSmallerTimeUnits!"days" == "hours");
    assert(nextSmallerTimeUnits!"hours" == "minutes");
    assert(nextSmallerTimeUnits!"minutes" == "seconds");
    assert(nextSmallerTimeUnits!"seconds" == "msecs");
    assert(nextSmallerTimeUnits!"msecs" == "usecs");
    assert(nextSmallerTimeUnits!"usecs" == "hnsecs");
    static assert(!__traits(compiles, nextSmallerTimeUnits!"hnsecs"));
}


/+
    The time units which are one step larger than the given units.
  +/
template nextLargerTimeUnits(string units)
    if(validTimeUnits(units) &&
       timeStrings.back != units)
{
    import std.algorithm : countUntil;
    enum nextLargerTimeUnits = timeStrings[countUntil(timeStrings, units) + 1];
}

unittest
{
    assert(nextLargerTimeUnits!"hnsecs" == "usecs");
    assert(nextLargerTimeUnits!"usecs" == "msecs");
    assert(nextLargerTimeUnits!"msecs" == "seconds");
    assert(nextLargerTimeUnits!"seconds" == "minutes");
    assert(nextLargerTimeUnits!"minutes" == "hours");
    assert(nextLargerTimeUnits!"hours" == "days");
    assert(nextLargerTimeUnits!"days" == "weeks");
    assert(nextLargerTimeUnits!"weeks" == "months");
    assert(nextLargerTimeUnits!"months" == "years");
    static assert(!__traits(compiles, nextLargerTimeUnits!"years"));
}


/+
    Returns the given hnsecs as an ISO string of fractional seconds.
  +/
static string fracSecsToISOString(int hnsecs) @safe pure nothrow
{
    import std.format : format;
    assert(hnsecs >= 0);

    try
    {
        if(hnsecs == 0)
            return "";

        string isoString = format(".%07d", hnsecs);

        while(isoString[$ - 1] == '0')
            isoString.popBack();

        return isoString;
    }
    catch(Exception e)
        assert(0, "format() threw.");
}

unittest
{
    assert(fracSecsToISOString(0) == "");
    assert(fracSecsToISOString(1) == ".0000001");
    assert(fracSecsToISOString(10) == ".000001");
    assert(fracSecsToISOString(100) == ".00001");
    assert(fracSecsToISOString(1000) == ".0001");
    assert(fracSecsToISOString(10_000) == ".001");
    assert(fracSecsToISOString(100_000) == ".01");
    assert(fracSecsToISOString(1_000_000) == ".1");
    assert(fracSecsToISOString(1_000_001) == ".1000001");
    assert(fracSecsToISOString(1_001_001) == ".1001001");
    assert(fracSecsToISOString(1_071_601) == ".1071601");
    assert(fracSecsToISOString(1_271_641) == ".1271641");
    assert(fracSecsToISOString(9_999_999) == ".9999999");
    assert(fracSecsToISOString(9_999_990) == ".999999");
    assert(fracSecsToISOString(9_999_900) == ".99999");
    assert(fracSecsToISOString(9_999_000) == ".9999");
    assert(fracSecsToISOString(9_990_000) == ".999");
    assert(fracSecsToISOString(9_900_000) == ".99");
    assert(fracSecsToISOString(9_000_000) == ".9");
    assert(fracSecsToISOString(999) == ".0000999");
    assert(fracSecsToISOString(9990) == ".000999");
    assert(fracSecsToISOString(99_900) == ".00999");
    assert(fracSecsToISOString(999_000) == ".0999");
}


/+
    Returns a Duration corresponding to to the given ISO string of
    fractional seconds.
  +/
static Duration fracSecsFromISOString(S)(in S isoString) @trusted pure
    if(isSomeString!S)
{
    import std.ascii : isDigit;
    import std.string : representation;
    import std.conv : to;
    import std.algorithm : all;

    if(isoString.empty)
        return Duration.zero;

    auto str = isoString.representation;

    enforce(str[0] == '.', new DateTimeException("Invalid ISO String"));
    str.popFront();

    enforce(!str.empty && str.length <= 7, new DateTimeException("Invalid ISO String"));
    enforce(all!isDigit(str), new DateTimeException("Invalid ISO String"));

    dchar[7] fullISOString = void;
    foreach(i, ref dchar c; fullISOString)
    {
        if(i < str.length)
            c = str[i];
        else
            c = '0';
    }

    return hnsecs(to!int(fullISOString[]));
}

unittest
{
    static void testFSInvalid(string isoString)
    {
        fracSecsFromISOString(isoString);
    }

    assertThrown!DateTimeException(testFSInvalid("."));
    assertThrown!DateTimeException(testFSInvalid("0."));
    assertThrown!DateTimeException(testFSInvalid("0"));
    assertThrown!DateTimeException(testFSInvalid("0000000"));
    assertThrown!DateTimeException(testFSInvalid(".00000000"));
    assertThrown!DateTimeException(testFSInvalid(".00000001"));
    assertThrown!DateTimeException(testFSInvalid("T"));
    assertThrown!DateTimeException(testFSInvalid("T."));
    assertThrown!DateTimeException(testFSInvalid(".T"));

    assert(fracSecsFromISOString("") == Duration.zero);
    assert(fracSecsFromISOString(".0000001") == hnsecs(1));
    assert(fracSecsFromISOString(".000001") == hnsecs(10));
    assert(fracSecsFromISOString(".00001") == hnsecs(100));
    assert(fracSecsFromISOString(".0001") == hnsecs(1000));
    assert(fracSecsFromISOString(".001") == hnsecs(10_000));
    assert(fracSecsFromISOString(".01") == hnsecs(100_000));
    assert(fracSecsFromISOString(".1") == hnsecs(1_000_000));
    assert(fracSecsFromISOString(".1000001") == hnsecs(1_000_001));
    assert(fracSecsFromISOString(".1001001") == hnsecs(1_001_001));
    assert(fracSecsFromISOString(".1071601") == hnsecs(1_071_601));
    assert(fracSecsFromISOString(".1271641") == hnsecs(1_271_641));
    assert(fracSecsFromISOString(".9999999") == hnsecs(9_999_999));
    assert(fracSecsFromISOString(".9999990") == hnsecs(9_999_990));
    assert(fracSecsFromISOString(".999999") == hnsecs(9_999_990));
    assert(fracSecsFromISOString(".9999900") == hnsecs(9_999_900));
    assert(fracSecsFromISOString(".99999") == hnsecs(9_999_900));
    assert(fracSecsFromISOString(".9999000") == hnsecs(9_999_000));
    assert(fracSecsFromISOString(".9999") == hnsecs(9_999_000));
    assert(fracSecsFromISOString(".9990000") == hnsecs(9_990_000));
    assert(fracSecsFromISOString(".999") == hnsecs(9_990_000));
    assert(fracSecsFromISOString(".9900000") == hnsecs(9_900_000));
    assert(fracSecsFromISOString(".9900") == hnsecs(9_900_000));
    assert(fracSecsFromISOString(".99") == hnsecs(9_900_000));
    assert(fracSecsFromISOString(".9000000") == hnsecs(9_000_000));
    assert(fracSecsFromISOString(".9") == hnsecs(9_000_000));
    assert(fracSecsFromISOString(".0000999") == hnsecs(999));
    assert(fracSecsFromISOString(".0009990") == hnsecs(9990));
    assert(fracSecsFromISOString(".000999") == hnsecs(9990));
    assert(fracSecsFromISOString(".0099900") == hnsecs(99_900));
    assert(fracSecsFromISOString(".00999") == hnsecs(99_900));
    assert(fracSecsFromISOString(".0999000") == hnsecs(999_000));
    assert(fracSecsFromISOString(".0999") == hnsecs(999_000));
}


/+
    Strips what RFC 5322, section 3.2.2 refers to as CFWS from the left-hand
    side of the given range (it strips comments delimited by $(D '(') and
    $(D ')') as well as folding whitespace).

    It is assumed that the given range contains the value of a header field and
    no terminating CRLF for the line (though the CRLF for folding whitespace is
    of course expected and stripped) and thus that the only case of CR or LF is
    in folding whitespace.

    If a comment does not terminate correctly (e.g. mismatched parens) or if the
    the FWS is malformed, then the range will be empty when stripCWFS is done.
    However, only minimal validation of the content is done (e.g. quoted pairs
    within a comment aren't validated beyond \$LPAREN or \$RPAREN, because
    they're inside a comment, and thus their value doesn't matter anyway). It's
    only when the content does not conform to the grammar rules for FWS and thus
    literally cannot be parsed that content is considered invalid, and an empty
    range is returned.

    Note that _stripCFWS is eager, not lazy. It does not create a new range.
    Rather, it pops off the CFWS from the range and returns it.
  +/
R _stripCFWS(R)(R range)
    if(isRandomAccessRange!R && hasSlicing!R && hasLength!R &&
       (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte)))
{
    immutable e = range.length;
    outer: for(size_t i = 0; i < e; )
    {
        switch(range[i])
        {
            case ' ': case '\t':
            {
                ++i;
                break;
            }
            case '\r':
            {
                if(i + 2 < e && range[i + 1] == '\n' && (range[i + 2] == ' ' || range[i + 2] == '\t'))
                {
                    i += 3;
                    break;
                }
                break outer;
            }
            case '\n':
            {
                if(i + 1 < e && (range[i + 1] == ' ' || range[i + 1] == '\t'))
                {
                    i += 2;
                    break;
                }
                break outer;
            }
            case '(':
            {
                ++i;
                size_t commentLevel = 1;
                while(i < e)
                {
                    if(range[i] == '(')
                        ++commentLevel;
                    else if(range[i] == ')')
                    {
                        ++i;
                        if(--commentLevel == 0)
                            continue outer;
                        continue;
                    }
                    else if(range[i] == '\\')
                    {
                        if(++i == e)
                            break outer;
                    }
                    ++i;
                }
                break outer;
            }
            default: return range[i .. e];
        }
    }
    return range[e .. e];
}

unittest
{
    import std.algorithm;
    import std.meta;
    import std.string;
    import std.typecons;

    foreach(cr; AliasSeq!(function(string a){return cast(ubyte[])a;},
                          function(string a){return map!(b => cast(char)b)(a.representation);}))
    (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
        scope(failure) writeln(typeof(cr).stringof);

        assert(_stripCFWS(cr("")).empty);
        assert(_stripCFWS(cr("\r")).empty);
        assert(_stripCFWS(cr("\r\n")).empty);
        assert(_stripCFWS(cr("\r\n ")).empty);
        assert(_stripCFWS(cr(" \t\r\n")).empty);
        assert(equal(_stripCFWS(cr(" \t\r\n hello")), cr("hello")));
        assert(_stripCFWS(cr(" \t\r\nhello")).empty);
        assert(_stripCFWS(cr(" \t\r\n\v")).empty);
        assert(equal(_stripCFWS(cr("\v \t\r\n\v")), cr("\v \t\r\n\v")));
        assert(_stripCFWS(cr("()")).empty);
        assert(_stripCFWS(cr("(hello world)")).empty);
        assert(_stripCFWS(cr("(hello world)(hello world)")).empty);
        assert(_stripCFWS(cr("(hello world\r\n foo\r where's\nwaldo)")).empty);
        assert(_stripCFWS(cr(" \t (hello \tworld\r\n foo\r where's\nwaldo)\t\t ")).empty);
        assert(_stripCFWS(cr("      ")).empty);
        assert(_stripCFWS(cr("\t\t\t")).empty);
        assert(_stripCFWS(cr("\t \r\n\r \n")).empty);
        assert(_stripCFWS(cr("(hello world) (can't find waldo) (he's lost)")).empty);
        assert(_stripCFWS(cr("(hello\\) world) (can't \\(find waldo) (he's \\(\\)lost)")).empty);
        assert(_stripCFWS(cr("(((((")).empty);
        assert(_stripCFWS(cr("(((()))")).empty);
        assert(_stripCFWS(cr("(((())))")).empty);
        assert(equal(_stripCFWS(cr("(((()))))")), cr(")")));
        assert(equal(_stripCFWS(cr(")))))")), cr(")))))")));
        assert(equal(_stripCFWS(cr("()))))")), cr("))))")));
        assert(equal(_stripCFWS(cr(" hello hello ")), cr("hello hello ")));
        assert(equal(_stripCFWS(cr("\thello (world)")), cr("hello (world)")));
        assert(equal(_stripCFWS(cr(" \r\n \\((\\))  foo")), cr("\\((\\))  foo")));
        assert(equal(_stripCFWS(cr(" \r\n (\\((\\)))  foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" \r\n (\\(()))  foo")), cr(")  foo")));
        assert(_stripCFWS(cr(" \r\n (((\\)))  foo")).empty);

        assert(_stripCFWS(cr("(hello)(hello)")).empty);
        assert(_stripCFWS(cr(" \r\n (hello)\r\n (hello)")).empty);
        assert(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n ")).empty);
        assert(_stripCFWS(cr("\t\t\t\t(hello)\t\t\t\t(hello)\t\t\t\t")).empty);
        assert(equal(_stripCFWS(cr(" \r\n (hello)\r\n (hello) \r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr("\t\r\n\t(hello)\r\n\t(hello)\t\r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr("\t\r\n\t(hello)\t\r\n\t(hello)\t\r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n \r\n (hello) \r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n \r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \r\n \r\n (hello)\t\r\n (hello) \r\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \r\n\t\r\n\t(hello)\t\r\n (hello) \r\n hello")), cr("hello")));

        assert(equal(_stripCFWS(cr(" (\r\n ( \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\t\r\n ( \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\r\n\t( \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n (\t\r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n (\r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n (\r\n\t) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n )\t\r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n )\r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n\t) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n ) \r\n foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n )\t\r\n foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n )\r\n foo")), cr("foo")));

        assert(equal(_stripCFWS(cr(" ( \r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\t\r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\r\n \r\n\t( \r\n \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\r\n \r\n( \r\n \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n\t) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n )\t\r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n )\r\n ) foo")), cr("foo")));

        assert(equal(_stripCFWS(cr(" ( \r\n bar \r\n ( \r\n bar \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n () \r\n ( \r\n () \r\n ) \r\n ) foo")), cr("foo")));
        assert(equal(_stripCFWS(cr(" ( \r\n \\\\ \r\n ( \r\n \\\\ \r\n ) \r\n ) foo")), cr("foo")));

        assert(_stripCFWS(cr("(hello)(hello)")).empty);
        assert(_stripCFWS(cr(" \n (hello)\n (hello) \n ")).empty);
        assert(_stripCFWS(cr(" \n (hello) \n (hello) \n ")).empty);
        assert(equal(_stripCFWS(cr(" \n (hello)\n (hello) \n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr("\t\n\t(hello)\n\t(hello)\t\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr("\t\n\t(hello)\t\n\t(hello)\t\n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \n (hello) \n \n (hello) \n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n \n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \n \n (hello)\t\n (hello) \n hello")), cr("hello")));
        assert(equal(_stripCFWS(cr(" \n\t\n\t(hello)\t\n (hello) \n hello")), cr("hello")));
    }();
}

// This is so that we don't have to worry about std.conv.to throwing. It also
// doesn't have to worry about quite as many cases as std.conv.to, since it
// doesn't have to worry about a sign on the value or about whether it fits.
T _convDigits(T, R)(R str)
    if(isIntegral!T && isSigned!T) // The constraints on R were already covered by parseRFC822DateTime.
{
    import std.ascii : isDigit;

    assert(!str.empty);
    T num = 0;
    foreach(i; 0 .. str.length)
    {
        if(i != 0)
            num *= 10;
        if(!isDigit(str[i]))
            return -1;
        num += str[i] - '0';
    }
    return num;
}

unittest
{
    import std.conv : to;
    import std.range;
    foreach(i; chain(iota(0, 101), [250, 999, 1000, 1001, 2345, 9999]))
    {
        scope(failure) writeln(i);
        assert(_convDigits!int(to!string(i)) == i);
    }
    foreach(str; ["-42", "+42", "1a", "1 ", " ", " 42 "])
    {
        scope(failure) writeln(str);
        assert(_convDigits!int(str) == -1);
    }
}


version(unittest)
{
    import std.typecons;
    import std.algorithm;
    //Variables to help in testing.
    Duration currLocalDiffFromUTC;
    immutable (TimeZone)[] testTZs;

    //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;

    Duration[] testFracSecs;

    SysTime[] testSysTimesBC;
    SysTime[] testSysTimesAD;

    //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()
    {
        immutable lt = LocalTime().utcToTZ(0);
        currLocalDiffFromUTC = dur!"hnsecs"(lt);

        immutable otherTZ = lt < 0 ? TimeZone.getTimeZone("Australia/Sydney")
                                   : TimeZone.getTimeZone("America/Denver");
        immutable ot = otherTZ.utcToTZ(0);

        auto diffs = [0L, lt, ot];
        auto diffAA = [0L : Rebindable!(immutable TimeZone)(UTC())];
        diffAA[lt] = Rebindable!(immutable TimeZone)(LocalTime());
        diffAA[ot] = Rebindable!(immutable TimeZone)(otherTZ);

        sort(diffs);
        testTZs = [diffAA[diffs[0]], diffAA[diffs[1]], diffAA[diffs[2]]];

        testFracSecs = [Duration.zero, hnsecs(1), hnsecs(5007), hnsecs(9999999)];

        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);
        }

        foreach(dt; testDateTimesBC)
        {
            foreach(tz; testTZs)
            {
                foreach(fs; testFracSecs)
                    testSysTimesBC ~= SysTime(dt, fs, tz);
            }
        }

        foreach(dt; testDateTimesAD)
        {
            foreach(tz; testTZs)
            {
                foreach(fs; testFracSecs)
                    testSysTimesAD ~= SysTime(dt, fs, tz);
            }
        }
    }
}


unittest
{
    /* Issue 6642 */
    static assert(!hasUnsharedAliasing!Date);
    static assert(!hasUnsharedAliasing!TimeOfDay);
    static assert(!hasUnsharedAliasing!DateTime);
    static assert(!hasUnsharedAliasing!SysTime);
}



// This script is for regenerating tzDatabaseNameToWindowsTZName and
// windowsTZNameToTZDatabaseName from
// http://unicode.org/cldr/data/common/supplemental/windowsZones.xml

/+
#!/bin/rdmd

import std.algorithm;
import std.array;
import std.conv;
import std.datetime;
import std.exception;
import std.path;
import std.stdio;
import std.string;

int main(string[] args)
{
    if(args.length != 4 || args[1].baseName != "windowsZones.xml")
    {
        stderr.writeln("genTZs.d windowsZones.xml <nix2WinFile> <win2NixFile>");
        return -1;
    }

    string[][string] win2Nix;
    string[][string] nix2Win;
    immutable f1 = `<mapZone other="`;
    immutable f2 = `type="`;

    auto file = File(args[1]);
    foreach(line; file.byLine())
    {
        line = line.find(f1);
        if(line.empty)
            continue;
        line = line[f1.length .. $];
        auto next = line.find('"');
        auto win = to!string(line[0 .. $ - next.length]);
        line = next.find(f2);
        line = line[f2.length .. $];
        next = line.find('"');
        auto nixes = to!string(line[0 .. $ - next.length]).split();

        if(auto l = win in win2Nix)
            *l ~= nixes;
        else
            win2Nix[win] = nixes;
        foreach(nix; nixes)
        {
            if(auto w = nix in nix2Win)
                *w ~= win;
            else
                nix2Win[nix] = [win];
        }
    }

    foreach(nix; nix2Win.byKey())
    {
        auto wins = nix2Win[nix];
        nix2Win[nix] = wins.sort().uniq().array();
    }

    foreach(win; win2Nix.byKey())
    {
        auto nixes = win2Nix[win];
        win2Nix[win] = nixes.sort().uniq().array();
    }

    // AFAIK, there should be no cases of a TZ Database time zone converting to
    // multiple windows time zones.
    foreach(nix, wins; nix2Win)
        enforce(wins.length == 1, format("%s -> %s", nix, wins));

    // We'll try to eliminate multiples by favoring a conversion if it's already
    // in Phobos, but if it's new, then the correct one will have to be chosen
    // manually from the results.
    string[] haveMultiple;
    foreach(win, nixes; win2Nix)
    {
        if(nixes.length > 1)
            haveMultiple ~= win;
    }
    bool[string] haveConflicts;
    foreach(win; haveMultiple)
    {
        if(auto curr = windowsTZNameToTZDatabaseName(win))
        {
            if(auto other = curr in nix2Win)
            {
                if((*other)[0] == win)
                {
                    win2Nix[win] = [curr];
                    continue;
                }
            }
        }
        haveConflicts[win] = true;
        writefln("Warning: %s -> %s", win, win2Nix[win]);
    }


    string[] nix2WinLines = [
        `string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc`,
        `{`,
        `    switch(tzName)`,
        `    {`];

    foreach(nix; nix2Win.keys.sort())
        nix2WinLines ~= format(`        case "%s": return "%s";`, nix, nix2Win[nix][0]);

    nix2WinLines ~= [
        `        default: return null;`,
        `    }`,
        `}`];


    string[] win2NixLines = [
        `string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc`,
        `{`,
        `    switch(tzName)`,
        `    {`];
    foreach(win; win2Nix.keys.sort())
    {
        immutable hasMultiple = cast(bool)(win in haveConflicts);
        foreach(nix; win2Nix[win])
            win2NixLines ~= format(`        case "%s": return "%s";%s`, win, nix, hasMultiple ? " FIXME" : "");
    }

    win2NixLines ~= [
        `        default: return null;`,
        `    }`,
        `}`];


    auto nix2WinFile = args[2];
    std.file.write(nix2WinFile, nix2WinLines.join("\n"));

    auto win2NixFile = args[3];
    std.file.write(win2NixFile, win2NixLines.join("\n"));

    return 0;
}
+/
