[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