--- date.d	2009-04-19 17:08:04.000000000 -0500
+++ datefix.d	2009-05-04 01:43:30.000000000 -0500
@@ -16,6 +16,9 @@
 // All Rights Reserved
 // written by Walter Bright
 // http://www.digitalmars.com
+//
+// Modified by Glenn Haecker, May 2009
+//
 
 module std.date;
 
@@ -45,15 +48,15 @@
  */
 struct Date
 {
-    int year = int.min;        /// use int.min as "nan" year value
-    int month;                /// 1..12
+    int year = int.min;     /// use int.min as "nan" year value
+    int month;              /// 1..12
     int day;                /// 1..31
-    int hour;                /// 0..23
-    int minute;                /// 0..59
-    int second;                /// 0..59
-    int ms;                /// 0..999
-    int weekday;        /// 0: not specified, 1..7: Sunday..Saturday
-    int tzcorrection = int.min;        /// -1200..1200 correction in hours
+    int hour;               /// 0..23
+    int minute;             /// 0..59
+    int second;             /// 0..59
+    int ms;                 /// 0..999
+    int weekday;            /// 0: not specified, 1..7: Sunday..Saturday
+    int tzcorrection = int.min;    /// -1200..1200 correction in hours
 
     /// Parse date out of string s[] and store it in this Date instance.
     void parse(string s)
@@ -160,44 +163,66 @@
 
 pure d_time floor(d_time d, int divisor)
 {
-    return (d < 0 ? d - divisor - 1 : d) / divisor;
+    return d / divisor;
 }
 
-int dmod(d_time n, d_time d)
-{   d_time r;
+pure int floor(int n, int divisor)
+{
+    return n / divisor;
+}
 
-    r = n % d;
+int dmod(d_time n, d_time d)
+{
+    d_time r = n % d;
     if (r < 0)
-        r += d;
+        r = -r;
     assert(cast(int)r == r);
     return cast(int)r;
 }
 
+d_time invertTime(d_time t)
+{
+    return cast(d_time) ticksPerDay - dmod(t, ticksPerDay);
+}
+
 int hourFromTime(d_time t)
 {
+    if (t < 0)
+        t = invertTime(t);
     return dmod(floor(t, msPerHour), hoursPerDay);
 }
 
 int minFromTime(d_time t)
 {
+    if (t < 0)
+        t = invertTime(t);
     return dmod(floor(t, msPerMinute), minutesPerHour);
 }
 
 int secFromTime(d_time t)
 {
+    if (t < 0)
+        t = invertTime(t);
     return dmod(floor(t, ticksPerSecond), 60);
 }
 
 int msFromTime(d_time t)
 {
-    return dmod(t / (ticksPerSecond / 1000), 1000);
+    int ms = dmod(t / (ticksPerSecond / 1000), 1000);
+    if (t < 0)
+    {
+        ms = 1000 - ms;
+        if (ms == 1000) ms = 0;
+    }
+    return ms;
 }
 
 int timeWithinDay(d_time t)
 {
-    return dmod(t, msPerDay);
+    return dmod(t, ticksPerDay);
 }
 
+// Placeholder? Future use?
 d_time toInteger(d_time n)
 {
     return n;
@@ -205,7 +230,20 @@
 
 int day(d_time t)
 {
-    return cast(int)floor(t, msPerDay);
+    d_time dy = floor(t, ticksPerDay);
+    if (t < 0 && (dy * ticksPerDay) <> t)
+        dy--;
+    return cast(int) dy;
+}
+
+unittest
+{
+    assert(day(1) == 0);
+    assert(day(-1) == -1);
+    assert(day(-1000) == -1);
+    assert(day(ticksPerDay) == 1);
+    assert(day(-ticksPerDay) == -1);
+    assert(day(-(ticksPerDay + 1)) == -2);
 }
 
 pure bool leapYear(int y)
@@ -214,6 +252,14 @@
             (y % 100 || (y % 400) == 0));
 }
 
+unittest
+{
+    bool[int] test = [1970:0, 1873:0, 1972:1, 1968:1, 2000:1, 1900:0];
+    foreach (int y, bool t; test)
+        assert(leapYear(y) == t,
+               text("leapYear test for ", y, " failed"));
+}
+
 int daysInYear(int y)
 {
     return 365 + leapYear(y);
@@ -221,15 +267,59 @@
 
 pure int dayFromYear(int y)
 {
-    return cast(int) (365 * (y - 1970) +
-                floor((y - 1969), 4) -
-                floor((y - 1901), 100) +
-                floor((y - 1601), 400));
+    static immutable int epoch = 1970;
+    static immutable int ly4   = floor(epoch, 4);
+    static immutable int ly100 = floor(epoch, 100);
+    static immutable int ly400 = floor(epoch, 400);
+
+    int lp = 0, bc = 0, adj = y;
+    if (y < epoch)
+    {
+        adj -= 1;
+        if (y <= 0)
+        {
+            bc = 1;
+            if (y < 0 && leapYear(y - 1)) lp = -1;
+        }
+    }
+    else lp = leapYear(y);
+
+    int d = ((365 * (y - epoch))
+             + (floor(adj, 4  ) - ly4)
+             - (floor(adj, 100) - ly100)
+             + (floor(adj, 400) - ly400) );
+
+    return d - lp - bc;
+}
+
+unittest
+{
+    // build reliable values
+    immutable int epoch = 1970;
+    int[int] yearDays = null;
+    yearDays[epoch] = 0;
+    int span = 11; // tested to 1_000_000
+    for( int i = 1; i < span; i++)
+    {
+        auto yr = epoch + i; // forward
+        yearDays[yr] = yearDays[yr - 1] + daysInYear(yr - 1);
+        yr = epoch -i;  // backward
+        yearDays[yr] = yearDays[yr + 1] - daysInYear(yr);
+    }
+    // check against them
+    foreach (int yr, int ckDays; yearDays)
+    {
+        auto days = dayFromYear(yr);
+        assert(days == ckDays,
+               text("dayFromYear() for ", yr, " failed\n",
+               "Returned ", days, ". Should be ", ckDays));
+    }
 }
 
+
 pure d_time timeFromYear(int y)
 {
-    return cast(d_time)msPerDay * dayFromYear(y);
+    return cast(d_time)ticksPerDay * dayFromYear(y);
 }
 
 /*****************************
@@ -263,6 +353,13 @@
     return y;
 }
 
+unittest
+{
+    assert(yearFromTime(1) == 1970);
+    assert(yearFromTime(0) == 1970);
+    assert(yearFromTime(-1) == 1969);
+}
+
 /*******************************
  * Determines if d_time t is a leap year.
  *
@@ -290,13 +387,13 @@
 int monthFromTime(d_time t)
 {
     auto year = yearFromTime(t);
-    auto day = day(t) - dayFromYear(year);
+    auto dy = day(t) - dayFromYear(year);
 
     int month;
-    if (day < 59)
+    if (dy < 59)
     {
-        if (day < 31)
-        {   assert(day >= 0);
+        if (dy < 31)
+        {   assert(dy >= 0, text("dy < 0, d_time = ", t));
             month = 0;
         }
         else
@@ -304,41 +401,50 @@
     }
     else
     {
-        day -= leapYear(year);
-        if (day < 212)
+        dy -= leapYear(year);
+        if (dy < 212)
         {
-            if (day < 59)
+            if (dy < 59)
                 month = 1;
-            else if (day < 90)
+            else if (dy < 90)
                 month = 2;
-            else if (day < 120)
+            else if (dy < 120)
                 month = 3;
-            else if (day < 151)
+            else if (dy < 151)
                 month = 4;
-            else if (day < 181)
+            else if (dy < 181)
                 month = 5;
             else
                 month = 6;
         }
         else
         {
-            if (day < 243)
+            if (dy < 243)
                 month = 7;
-            else if (day < 273)
+            else if (dy < 273)
                 month = 8;
-            else if (day < 304)
+            else if (dy < 304)
                 month = 9;
-            else if (day < 334)
+            else if (dy < 334)
                 month = 10;
-            else if (day < 365)
+            else if (dy < 365)
                 month = 11;
             else
-                assert(0);
+              {  assert(0, text("year/dy ", year , "/", dy, " failed"));}
         }
     }
+
     return month;
 }
 
+unittest
+{
+    assert(monthFromTime(1) == 0);
+    assert(monthFromTime(0) == 0);
+    assert(monthFromTime(-1) == 11);
+}
+
+
 /*******************************
  * Compute which day in a month a d_time t is.
  * Returns:
@@ -346,31 +452,51 @@
  */
 int dateFromTime(d_time t)
 {
+    return dateFromTime(t, monthFromTime(t));
+}
+
+/**************
+ * Overloaded with month supplied
+ * No need to calc if it's already known.
+ * Params:
+ *        t     = d_time
+ *        month = 0..11, as returned from monthFromTime()
+ * Returns:
+ *        Integer in the range 1..31
+ */
+int dateFromTime(d_time t, int month)
+{
     auto year = yearFromTime(t);
-    auto day = day(t) - dayFromYear(year);
+    auto dy   = day(t) - dayFromYear(year);
     auto leap = leapYear(year);
-    auto month = monthFromTime(t);
     int date;
     switch (month)
     {
-        case 0:         date = day +   1;                break;
-        case 1:         date = day -  30;                break;
-        case 2:         date = day -  58 - leap;        break;
-        case 3:         date = day -  89 - leap;        break;
-        case 4:         date = day - 119 - leap;        break;
-        case 5:         date = day - 150 - leap;        break;
-        case 6:         date = day - 180 - leap;        break;
-        case 7:         date = day - 211 - leap;        break;
-        case 8:         date = day - 242 - leap;        break;
-        case 9:         date = day - 272 - leap;        break;
-        case 10: date = day - 303 - leap;        break;
-        case 11: date = day - 333 - leap;        break;
+        case 0:  date = dy +   1;          break;
+        case 1:  date = dy -  30;          break;
+        case 2:  date = dy -  58 - leap;   break;
+        case 3:  date = dy -  89 - leap;   break;
+        case 4:  date = dy - 119 - leap;   break;
+        case 5:  date = dy - 150 - leap;   break;
+        case 6:  date = dy - 180 - leap;   break;
+        case 7:  date = dy - 211 - leap;   break;
+        case 8:  date = dy - 242 - leap;   break;
+        case 9:  date = dy - 272 - leap;   break;
+        case 10: date = dy - 303 - leap;   break;
+        case 11: date = dy - 333 - leap;   break;
         default:
-            assert(0);
+            assert(0, text("month = ", month));
     }
     return date;
 }
 
+unittest
+{
+    assert(dateFromTime( 1,  0) ==  1);
+    assert(dateFromTime( 0,  0) ==  1);
+    assert(dateFromTime(-1, 11) == 31);
+}
+
 /*******************************
  * Compute which day of the week a d_time t is.
  * Returns:
@@ -385,6 +511,41 @@
     return w;
 }
 
+unittest
+{
+    d_time t = parse("1969-12-25 00:00:00 UTC");
+    int wd = 4; // Thu
+
+    for (int i = 0; i < 15; i++)
+    {
+        assert(weekDay(t) == wd, text(toUTCString(t), " weekDay ", weekDay(t), ", should be ", wd));
+        t += cast(d_time) ticksPerDay;
+        wd++;
+        if (wd > 6) wd = 0;
+    }
+
+    d_time t2;
+    int[12] wds;
+
+    wds = [4,0,1, 4,6,2, 4,0,3, 5,1,3];
+    t = parse("2004-01-01 00:00:00 UTC");
+    for (int i = 0; i < 12; i++)
+    {
+        t2 = addMonths(t, i);
+        wd = weekDay(t2);
+        assert(wd == wds[i], text(toUTCString(t2), " weekDay ", wd, ", should be ", wds[i]));
+    }
+
+    wds = [3,6,6, 2,4,0, 2,5,1, 3,6,1];
+    t = parse("2003-01-01 00:00:00 UTC");
+    for (int i = 0; i < 12; i++)
+    {
+        t2 = addMonths(t, i);
+        wd = weekDay(t2);
+        assert(wd == wds[i], text(toUTCString(t2), " weekDay ", wd, ", should be ", wds[i]));
+    }
+}
+
 /***********************************
  * Convert from UTC to local time.
  */
@@ -419,7 +580,7 @@
            ms * ticksPerMs;
 }
 
-/* *****************************
+/******************************
  * Params:
  *        month = 0..11
  *        date = day of month, 1..31
@@ -433,13 +594,13 @@
     const m = dmod(month, 12);
 
     const leap = leapYear(y);
-    auto t = timeFromYear(y) + cast(d_time) mdays[m] * msPerDay;
+    auto t = timeFromYear(y) + cast(d_time) mdays[m] * ticksPerDay;
     if (leap && month >= 2)
-        t += msPerDay;
+        t += ticksPerDay;
 
     if (yearFromTime(t) != y ||
         monthFromTime(t) != m ||
-        dateFromTime(t) != 1)
+        dateFromTime(t, m) != 1)
     {
         return  d_time_nan;
     }
@@ -455,11 +616,10 @@
     return day * ticksPerDay + time;
 }
 
+// Placeholder? Future use?
 d_time timeClip(d_time time)
 {
-    //printf("TimeClip(%g) = %g\n", time, toInteger(time));
-
-    return toInteger(time);
+    return time;
 }
 
 /***************************************
@@ -486,7 +646,6 @@
 {
     // Get day of the first of the month
     auto x = makeDay(year, month - 1, 1);
-
     // Get the week day 0..6 of the first of this month
     auto wd = weekDay(makeDate(x, 0));
 
@@ -597,15 +756,16 @@
 
     //printf("hr = %d, offset = %g, localTZA = %g, dst = %g, + = %g\n", hr, offset, localTZA, dst, localTZA + dst);
 
+    auto month = monthFromTime(t);
     auto len = sprintf(buffer.ptr,
             "%.3s %.3s %02d %02d:%02d:%02d GMT%c%02d%02d %d",
             &daystr[weekDay(t) * 3],
-            &monstr[monthFromTime(t) * 3],
-            dateFromTime(t),
+            &monstr[month * 3],
+            dateFromTime(t, month),
             hourFromTime(t), minFromTime(t), secFromTime(t),
             sign, hr, mn,
             cast(long)yearFromTime(t));
-    
+
     // Ensure no buggy buffer overflows
     //printf("len = %d, buffer.length = %d\n", len, buffer.length);
     assert(len < buffer.length);
@@ -627,15 +787,16 @@
     if (t == d_time_nan)
         return "Invalid Date";
 
+    int mon = monthFromTime(t);
     auto len = sprintf(buffer.ptr, "%.3s, %02d %.3s %d %02d:%02d:%02d UTC",
-            &daystr[weekDay(t) * 3], dateFromTime(t),
-            &monstr[monthFromTime(t) * 3],
+            &daystr[weekDay(t) * 3], dateFromTime(t, mon),
+            &monstr[mon * 3],
             yearFromTime(t),
             hourFromTime(t), minFromTime(t), secFromTime(t));
-    
+
     // Ensure no buggy buffer overflows
     assert(len < buffer.length);
-    
+
     return cast(string) buffer[0 .. len];
 }
 
@@ -658,10 +819,11 @@
     auto offset = localTZA + dst;
     auto t = time + offset;
 
+    int mon = monthFromTime(t);
     auto len = sprintf(buffer.ptr, "%.3s %.3s %02d %d",
         &daystr[weekDay(t) * 3],
-        &monstr[monthFromTime(t) * 3],
-        dateFromTime(t),
+        &monstr[mon * 3],
+        dateFromTime(t, mon),
         cast(long)yearFromTime(t));
 
     // Ensure no buggy buffer overflows
@@ -734,7 +896,7 @@
         }
         auto day = makeDay(dp.year, dp.month - 1, dp.day);
         auto result = makeDate(day,time);
-        return timeClip(result);
+        return result;
     }
     catch
     {
@@ -818,7 +980,7 @@
             time = makeTime(st.wHour, st.wMinute, 0, 0);
         }
         auto n = makeDate(day,time);
-        return timeClip(n);
+        return n;
     }
 
     d_time getLocalTZA()
@@ -919,7 +1081,7 @@
     d_time getLocalTZA()
     {
         time_t t;
-        
+
         time(&t);
         version (OSX)
         {
@@ -969,9 +1131,9 @@
                 d_time dstt(int year, int month, int week, int weekday, int hour)
                 {
                     auto mday = dateFromNthWeekdayOfMonth(year,  month, weekday, week);
-                    return timeClip(makeDate(
+                    return makeDate(
                         makeDay(year, month - 1, mday),
-                        makeTime(hour, 0, 0, 0)));
+                        makeTime(hour, 0, 0, 0));
                 }
 
                 d_time start;
@@ -1028,15 +1190,16 @@
 
     d_time t;
 
-    t = std.date.makeDate(std.date.makeDay(year, month, dayofmonth),
-            std.date.makeTime(hour, minute, second, 0));
+    t = makeDate(makeDay(year, month, dayofmonth),
+            makeTime(hour, minute, second, 0));
 
-    assert(yearFromTime(t) == year);
-    assert(monthFromTime(t) == month);
-    assert(dateFromTime(t) == dayofmonth);
-    assert(hourFromTime(t) == hour);
-    assert(minFromTime(t) == minute);
-    assert(secFromTime(t) == second);
+    int mon = monthFromTime(t);
+    assert(yearFromTime(t)      == year);
+    assert(mon                  == month);
+    assert(dateFromTime(t, mon) == dayofmonth);
+    assert(hourFromTime(t)      == hour);
+    assert(minFromTime(t)       == minute);
+    assert(secFromTime(t)       == second);
 
     t -= localTZA + daylightSavingTA(t);
 
@@ -1055,9 +1218,10 @@
 
     t += localTZA + daylightSavingTA(t);
 
+    uint mon = monthFromTime(t);
     uint year = yearFromTime(t);
-    uint month = monthFromTime(t);
-    uint dayofmonth = dateFromTime(t);
+    uint month = mon;
+    uint dayofmonth = dateFromTime(t, mon);
     uint hour = hourFromTime(t);
     uint minute = minFromTime(t);
     uint second = secFromTime(t);
@@ -1132,3 +1296,148 @@
     //writeln(r);
 }
 
+
+/**************
+ * Database-styole DateTime string from d_time
+ * Params:
+ *        t = d_time
+ * Returns:
+ *        string formatted as YYYY-MM-DD HH:mm:ss
+ * Author:
+ *        Glenn Haecker, May 2009
+ */
+string toDbDateTime(d_time t)
+{
+    auto yr = yearFromTime(t);
+    auto mh = monthFromTime(t) + 1;
+    auto dy = dateFromTime(t, mh - 1);
+    auto hr = hourFromTime(t);
+    auto me = minFromTime(t);
+    auto sd = secFromTime(t);
+
+    return std.string.format("%d-%02d-%02d %02d:%02d:%02d", yr, mh, dy, hr, me, sd);
+}
+
+unittest
+{
+    string scmp;
+    d_time t;
+    string[] testdt = [
+        "1969-12-31 23:59:58",
+        "1969-12-31 23:59:59",
+        "1970-01-01 00:00:00", // epoch
+        "1970-01-01 00:00:01",
+        "1970-01-01 00:00:02"];
+
+    foreach(int i, string s; testdt)
+    {
+        t = parse(s ~ " UTC");
+        scmp = toDbDateTime(t);
+        //writefln("%s %d\n%s %d", s, t, scmp, t);
+        assert(scmp == s, text(s," failed"));
+    }
+}
+
+
+/*******************
+ * Adjust d_time forward or backward by years
+ * Params:
+ *        t     = d_time
+ *        years = signed number of years
+ * Returns:
+ *        d_time adjusted by years
+ */
+d_time addYears(d_time t, int years)
+{
+    if (years == 0) return t;
+
+    int yr = yearFromTime(t);
+    int newDays, days = day(t) - dayFromYear(yr);
+    int sep = 59 + leapYear(yr);
+    if (days < sep)
+        newDays = dayFromYear(yr + years) + days;
+    else
+        newDays = dayFromYear(yr + years + 1) - (daysInYear(yr) - days);
+    return cast(d_time) ticksPerDay * newDays;
+}
+
+
+/*******************
+ * Adjust d_time forward or backward by months
+ * Params:
+ *        t      = d_time
+ *        months = signed number of months
+ * Returns:
+ *        d_time adjusted by months
+ */
+d_time addMonths(d_time t, int months)
+{
+    if (months == 0) return t;
+
+    int days = 0;
+    auto yr = yearFromTime(t);
+    auto mon = monthFromTime(t); // zero-based month
+
+    int y = cast(int) floor(months, 12);
+    if (y <> 0)
+    {
+        t = addYears(t, y);
+        months %= 12;
+        yr += y;
+    }
+
+    while (months > 0)
+    {
+        days += daysInMonth(yr, mon + 1);
+        mon++;
+        if (mon > 11)
+        {
+            mon = 0;
+            yr++;
+        }
+        months--;
+    }
+
+    while (months < 0)
+    {
+        if (mon == 0)
+            days -= daysInMonth(yr - 1, 12);
+        else
+            days -= daysInMonth(yr, mon);
+        mon--;
+        if (mon < 0)
+        {
+            mon = 11;
+            yr--;
+        }
+        months++;
+    }
+    return t += cast(d_time) ticksPerDay * days;
+}
+
+unittest
+{
+    // verify 19-month roundtrip
+    int[] years = [
+        2005, // - 2007 no leap year
+        1969, // - 1971 straddle epoch
+        1909, // - 1911 no leap year, neg d_time
+        1910, // - 1912 leap year, neg d_time
+        1899, // - 1901 no centruy leap year, neg d_time
+        1599, // - 1601 distant past
+        2399  // - 2401 distant future
+        ];
+
+    d_time dt1, dt2, dt3, dt4;
+    foreach (int y; years)
+    {
+        dt1 = parse(text(y,     "-10-31"));
+        dt2 = parse(text(y + 2, "-05-31"));
+        dt3 = addMonths(dt1, 19);
+        assert(dt3 == dt2,
+            text("year: ", y, " - ", dt3, " != ", dt2, " diff(", dt2 - dt3, ")"));
+        dt4 = addMonths(dt3, -19);
+        assert(dt4 == dt1,
+            text("year: ", y, " - ", dt4, " != ", dt1, " diff(", dt1 - dt4, ")"));
+    }
+}
