[Issue 2917] New: std.date fails for all years before 1970

d-bugmail at puremagic.com d-bugmail at puremagic.com
Thu Apr 30 08:22:47 PDT 2009


http://d.puremagic.com/issues/show_bug.cgi?id=2917

           Summary: std.date fails for all years before 1970
           Product: D
           Version: 2.029
          Platform: PC
        OS/Version: Linux
            Status: NEW
          Severity: major
          Priority: P2
         Component: Phobos
        AssignedTo: bugzilla at digitalmars.com
        ReportedBy: ghaecker at idworld.net


For years prior to 1970, conversion from string to d_time returns d_time_nan. 
Strings are parsed correctly, but conversion to d_time fails the test at line
440 of date.d in the makeDay() function.

Commenting out lines 440 through 445 allows a d_time to be returned, but the
value is skewed.  Line 447 works for years >= 1970:

    return day(t) + date - 1;

but for years < 1970, this adjustment:

    return day(t) + date;

returns the expected value.

There is also another problem: attempting to convert a negative d_time back to
a string requires adding ticksPerSecond to d_time to return the original value
from secFromTime(), which, of course, trashes the ms value.  Adding the value
of  ticksPerSecond appears to work for all negative d_time values, except for
-1000.  

NOTE: I was only working with a resolution of seconds, so the effect on
milliseconds/ticks was not taken into account.

I'm new to D, so please don't be too critical of my code.  I'm including it so
you can see the kludges I added to work around these issues.  BTW, an
addMonths() function (more elegant than mine) would be a welcome addition to
std.date.  My attempt at writing one along with a unittest section was what
revealed this bug.  Unittest is a brilliant addition to the language.

import std.stdio,
    std.date,
    std.conv,
    std.random;

int main(char[][] args)
{
    bool pass;
    int adj;
    string tscmp;
    d_time tt, rtt;

    string[] testdates = [
        "1969-12-31 23:59:58",
        "1969-12-31 23:59:59",
        "1970-01-01 00:00:00",
        "1970-01-01 00:00:01",
        "1970-01-01 00:00:02"
        ];
    writefln("Crossing the 1970 epoch...\n");
    foreach(int i, string s; testdates) {
        tt = dbgParse(s ~ " GMT");
        // KLUDGE
        adj = (i < 2) ? ticksPerSecond - 5: 0;
        rtt = tt + adj;
        tscmp = toDbDateTime(rtt);
        pass = (tscmp == s);
        writefln("%s -> %5d", s, tt);
        writefln("%s <- %5d   %s\n", tscmp, rtt, pass);
    }

    // Random tests
    auto gen = Mt19937(unpredictableSeed);
    int yr, mh, dy, hr, me, sd, days;
    int yr2, mh2, dy2, hr2, me2, sd2;
    int tested, passed, before1970, show = 7;
    string ts;
    bool first;
    for (int i; i < 10000; i++) {
        tested++;
        yr = uniform(1601, 2400, gen);
        if (yr < 1970) before1970++;
        mh = uniform(1, 12, gen);
        days = daysInMonth(yr, mh);
        dy = uniform(1, days, gen);
        hr = uniform(0, 23, gen);
        me = uniform(0, 59, gen);
        sd = uniform(0, 59, gen);
        ts = std.string.format("%d-%02d-%02d %02d:%02d:%02d", yr, mh, dy, hr,
me, sd);
        tt = dbgParse(ts ~ " GMT");
        // KLUDGE
        adj = (yr < 1970) ? ticksPerSecond - 5: 0;
        rtt = tt + adj;
        tscmp = toDbDateTime(rtt);
        pass = (ts == tscmp);
        if (! pass || show > 0) {
            if (! first) {
                first = true;
                writefln("Showing first %d random samples...\n", show);
            }
            show--;
            writefln("%s -> %16d", ts, tt);
            writefln("%s <- %16d   %s\n", tscmp, rtt, pass);
        }
        if (pass) passed++;
    }

    writefln("");
    writefln("Tested: %7d", tested);
    writefln("< 1970: %7d", before1970);
    writefln("Passed: %7d", passed);

    return 0;
}

string toDbDateTime(d_time t) {
    auto yr = yearFromTime(t);
    auto mh = monthFromTime(t) + 1;
    auto dy = dateFromTime(t);
    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);
}

d_time dbgParse(string s)
{
    try
    {
        Date dp;
        dp.parse(s);
        auto time = makeTime(dp.hour, dp.minute, dp.second, dp.ms);
        if (dp.tzcorrection == int.min)
            time -= localTZA;
        else
        {
            time += cast(d_time)(dp.tzcorrection / 100) * msPerHour +
                    cast(d_time)(dp.tzcorrection % 100) * msPerMinute;
        }
        auto day = dbgMakeDay(dp.year, dp.month - 1, dp.day);
        d_time result = makeDate(day,time);
        return timeClip(result);
    }
    catch
    {
        return d_time_nan;                // erroneous date string
    }
}

d_time dbgMakeDay(d_time year, d_time month, d_time date)
{

    int[12] mdays =
        [ 0,31,59,90,120,151,181,212,243,273,304,334 ];
    const y = cast(int)(year + floor(month, 12));
    const m = dmod(month, 12);

    const leap = leapYear(y);
    auto t = timeFromYear(y) + cast(d_time) mdays[m] * msPerDay;
    if (leap && month >= 2)
        t += msPerDay;

    /* DISABLED: this test failes for y < 1970

    if (yearFromTime(t) != y ||
        monthFromTime(t) != m ||
        dateFromTime(t) != 1)
    {
        writefln("Bad match");
        writefln("%d : %d, %d : %d, %d : %d", y, yearFromTime(t), m,
monthFromTime(t), 1, dateFromTime(t));

        return  d_time_nan;
    }

    */

    // KLUDGE
    int adj = (y < 1970) ? 0 : 1;

    return day(t) + date - adj;

}

d_time addMonths(d_time dt, int months) {
    int days = 0;
    if (months <> 0) {
        int mon = monthFromTime(dt); // zero-based month
        int yr = yearFromTime(dt);
        while (months > 0) {
            days += daysInMonth(yr, mon + 1); // one-based month
            mon++;
            if (mon > 11) {
                mon = 0;
                yr++;
            }
            months--;
        }
        while (months < 0) {
            mon--;
            if (mon < 0) {
                mon = 11;
                yr--;
            }
            days -= daysInMonth(yr, mon + 1);  // one-based month
            months++;
        }
    }
    return dt += days * cast(d_time) ticksPerDay;
}

unittest {

    d_time dt1, dt2, dt3, dt4;
    dt1 = dbgParse("1979-10-31 13:14:15 GMT");
    dt2 = dbgParse("1981-05-31 13:14:15 GMT");
    dt3 = addMonths(dt1, 19);
    assert(dt3 == dt2, text(dt3, " != ", dt2));
    dt4 = addMonths(dt3, -19);
    assert(dt4 == dt1, text(dt4, " != ", dt1));

    dt1 = dbgParse("1969-10-31 13:14:15 GMT");
    dt2 = dbgParse("1971-05-31 13:14:15 GMT");
    dt3 = addMonths(dt1, 19);
    assert(dt3 == dt2, text(dt3, " != ", dt2));
    dt4 = addMonths(dt3, -19);
    assert(dt4 == dt1, text(dt4, " != ", dt1));

    dt1 = dbgParse("1599-10-31 13:14:15 GMT");
    dt2 = dbgParse("1601-05-31 13:14:15 GMT");
    dt3 = addMonths(dt1, 19);
    assert(dt3 == dt2, text(dt3, " != ", dt2));
    dt4 = addMonths(dt3, -19);
    assert(dt4 == dt1, text(dt4, " != ", dt1));

    dt1 = dbgParse("2399-10-31 13:14:15 GMT");
    dt2 = dbgParse("2401-05-31 13:14:15 GMT");
    dt3 = addMonths(dt1, 19);
    assert(dt3 == dt2, text(dt3, " != ", dt2));
    dt4 = addMonths(dt3, -19);
    assert(dt4 == dt1, text(dt4, " != ", dt1));

}


-- 



More information about the Digitalmars-d-bugs mailing list