[ADD] arithmetics between date and timedelta
bzr revid: xmo@openerp.com-20121123095206-dtvoiignb7xnzsl0
This commit is contained in:
parent
815b908759
commit
328537a7ba
|
@ -41,7 +41,177 @@ openerp.web.pyeval = function (instance) {
|
|||
return fn(mod, Math.floor(x));
|
||||
};
|
||||
var zero = py.float.fromJSON(0);
|
||||
|
||||
// Port from pypy/lib_pypy/datetime.py
|
||||
var DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
var DAYS_BEFORE_MONTH = [null];
|
||||
var dbm = 0;
|
||||
for (var i=1; i<DAYS_IN_MONTH.length; ++i) {
|
||||
DAYS_BEFORE_MONTH.push(dbm);
|
||||
dbm += DAYS_IN_MONTH[i];
|
||||
}
|
||||
var is_leap = function (year) {
|
||||
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
||||
};
|
||||
var days_before_year = function (year) {
|
||||
var y = year - 1;
|
||||
return y*365 + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400);
|
||||
};
|
||||
var days_in_month = function (year, month) {
|
||||
if (month === 2 && is_leap(year)) {
|
||||
return 29;
|
||||
}
|
||||
return DAYS_IN_MONTH[month];
|
||||
};
|
||||
var days_before_month = function (year, month) {
|
||||
var post_leap_feb = month > 2 && is_leap(year);
|
||||
return DAYS_BEFORE_MONTH[month]
|
||||
+ (post_leap_feb ? 1 : 0);
|
||||
};
|
||||
var ymd2ord = function (year, month, day) {
|
||||
var dim = days_in_month(year, month);
|
||||
if (!(1 <= day && day <= dim)) {
|
||||
throw new Error("ValueError: day must be in 1.." + dim);
|
||||
}
|
||||
return days_before_year(year)
|
||||
+ days_before_month(year, month)
|
||||
+ day;
|
||||
};
|
||||
var DI400Y = days_before_year(401);
|
||||
var DI100Y = days_before_year(101);
|
||||
var DI4Y = days_before_year(5);
|
||||
var assert = function (bool) {
|
||||
if (!bool) {
|
||||
throw new Error("AssertionError");
|
||||
}
|
||||
};
|
||||
var ord2ymd = function (n) {
|
||||
--n;
|
||||
var n400, n100, n4, n1, n0;
|
||||
divmod(n, DI400Y, function (_n400, n) {
|
||||
n400 = _n400;
|
||||
divmod(n, DI100Y, function (_n100, n) {
|
||||
n100 = _n100;
|
||||
divmod(n, DI4Y, function (_n4, n) {
|
||||
n4 = _n4;
|
||||
divmod(n, 365, function (_n1, n) {
|
||||
n1 = _n1;
|
||||
n0 = n;
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
n = n0;
|
||||
var year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1;
|
||||
if (n1 == 4 || n100 == 100) {
|
||||
assert(n0 === 0);
|
||||
return {
|
||||
year: year - 1,
|
||||
month: 12,
|
||||
day: 31
|
||||
};
|
||||
}
|
||||
|
||||
var leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);
|
||||
assert(leapyear == is_leap(year));
|
||||
var month = (n + 50) >> 5;
|
||||
var preceding = DAYS_BEFORE_MONTH[month] + ((month > 2 && leapyear) ? 1 : 0);
|
||||
if (preceding > n) {
|
||||
--month;
|
||||
preceding -= DAYS_IN_MONTH[month] + ((month === 2 && leapyear) ? 1 : 0);
|
||||
}
|
||||
n -= preceding;
|
||||
return {
|
||||
year: year,
|
||||
month: month,
|
||||
day: n+1
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the stuff passed in into a valid date, applying overflows as needed
|
||||
*/
|
||||
var tmxxx = function (year, month, day, hour, minute, second, microsecond) {
|
||||
hour = hour || 0; minute = minute || 0; second = second || 0;
|
||||
microsecond = microsecond || 0;
|
||||
|
||||
if (microsecond < 0 || microsecond > 999999) {
|
||||
divmod(microsecond, 1000000, function (carry, ms) {
|
||||
microsecond = ms;
|
||||
second += carry
|
||||
});
|
||||
}
|
||||
if (second < 0 || second > 59) {
|
||||
divmod(second, 60, function (carry, s) {
|
||||
second = s;
|
||||
minute += carry;
|
||||
});
|
||||
}
|
||||
if (minute < 0 || minute > 59) {
|
||||
divmod(minute, 60, function (carry, m) {
|
||||
minute = m;
|
||||
hour += carry;
|
||||
})
|
||||
}
|
||||
if (hour < 0 || hour > 23) {
|
||||
divmod(hour, 24, function (carry, h) {
|
||||
hour = h;
|
||||
day += carry;
|
||||
})
|
||||
}
|
||||
// That was easy. Now it gets muddy: the proper range for day
|
||||
// can't be determined without knowing the correct month and year,
|
||||
// but if day is, e.g., plus or minus a million, the current month
|
||||
// and year values make no sense (and may also be out of bounds
|
||||
// themselves).
|
||||
// Saying 12 months == 1 year should be non-controversial.
|
||||
if (month < 1 || month > 12) {
|
||||
divmod(month-1, 12, function (carry, m) {
|
||||
month = m + 1;
|
||||
year += carry;
|
||||
})
|
||||
}
|
||||
// Now only day can be out of bounds (year may also be out of bounds
|
||||
// for a datetime object, but we don't care about that here).
|
||||
// If day is out of bounds, what to do is arguable, but at least the
|
||||
// method here is principled and explainable.
|
||||
var dim = days_in_month(year, month);
|
||||
if (day < 1 || day > dim) {
|
||||
// Move day-1 days from the first of the month. First try to
|
||||
// get off cheap if we're only one day out of range (adjustments
|
||||
// for timezone alone can't be worse than that).
|
||||
if (day === 0) {
|
||||
--month;
|
||||
if (month > 0) {
|
||||
day = days_in_month(year, month);
|
||||
} else {
|
||||
--year; month=12; day=31;
|
||||
}
|
||||
} else if (day == dim + 1) {
|
||||
++month;
|
||||
day = 1;
|
||||
if (month > 12) {
|
||||
month = 1;
|
||||
++year;
|
||||
}
|
||||
} else {
|
||||
var r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));
|
||||
year = r.year;
|
||||
month = r.month;
|
||||
day = r.day;
|
||||
}
|
||||
}
|
||||
return {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
};
|
||||
};
|
||||
datetime.timedelta = py.type('timedelta', null, {
|
||||
__init__: function () {
|
||||
var args = py.PY_parseArgs(arguments, [
|
||||
|
@ -274,7 +444,40 @@ openerp.web.pyeval = function (instance) {
|
|||
var d = new Date();
|
||||
return py.PY_call(
|
||||
datetime.date, [d.getFullYear(), d.getMonth() + 1, d.getDate()]);
|
||||
})
|
||||
}),
|
||||
__eq__: function (other) {
|
||||
return (this.year === other.year
|
||||
&& this.month === other.month
|
||||
&& this.day === other.day)
|
||||
? py.True : py.False;
|
||||
},
|
||||
__add__: function (other) {
|
||||
if (py.PY_call(py.isinstance, [other, datetime.timedelta]) !== py.True) {
|
||||
return py.NotImplemented;
|
||||
}
|
||||
var s = tmxxx(this.year, this.month, this.day + other.days);
|
||||
console.log(this, other, s);
|
||||
return datetime.date.fromJSON(s.year, s.month, s.day);
|
||||
},
|
||||
__radd__: function (other) { return this.__add__(other); },
|
||||
__sub__: function (other) {
|
||||
if (py.PY_call(py.isinstance, [other, datetime.timedelta]) === py.True) {
|
||||
return this.__add__(other.__neg__());
|
||||
}
|
||||
if (py.PY_call(py.isinstance, [other, datetime.date]) === py.True) {
|
||||
// FIXME: getattr and sub API methods
|
||||
return py.PY_call(datetime.timedelta, [
|
||||
this.toordinal().__sub__(other.toordinal())
|
||||
]);
|
||||
}
|
||||
return py.NotImplemented;
|
||||
},
|
||||
toordinal: function () {
|
||||
return py.float.fromJSON(ymd2ord(this.year, this.month, this.day));
|
||||
},
|
||||
fromJSON: function (year, month, day) {
|
||||
return py.PY_call(datetime.date, [year, month, day])
|
||||
}
|
||||
});
|
||||
datetime.time = py.type('time', null, {
|
||||
__init__: function () {
|
||||
|
|
|
@ -163,6 +163,87 @@ openerp.testing.section('eval.types', {
|
|||
ok(py.eval('bool(td(microseconds=1))', c));
|
||||
ok(py.eval('bool(not td(0))', c));
|
||||
});
|
||||
|
||||
test('date.test_computations', function (instance) {
|
||||
var d = instance.web.pyeval.context().datetime;
|
||||
|
||||
var a = d.date.fromJSON(2002, 1, 31);
|
||||
var b = d.date.fromJSON(1956, 1, 31);
|
||||
strictEqual(
|
||||
py.eval('(a - b).days', {a: a, b: b}),
|
||||
46 * 365 + 12);
|
||||
strictEqual(py.eval('(a - b).seconds', {a: a, b: b}), 0);
|
||||
strictEqual(py.eval('(a - b).microseconds', {a: a, b: b}), 0);
|
||||
|
||||
var day = py.PY_call(d.timedelta, [py.float.fromJSON(1)]);
|
||||
var week = py.PY_call(d.timedelta, [py.float.fromJSON(7)]);
|
||||
a = d.date.fromJSON(2002, 3, 2);
|
||||
var ctx = {
|
||||
a: a,
|
||||
day: day,
|
||||
week: week,
|
||||
date: d.date
|
||||
};
|
||||
ok(py.eval('a + day == date(2002, 3, 3)', ctx));
|
||||
ok(py.eval('day + a == date(2002, 3, 3)', ctx)); // 5
|
||||
ok(py.eval('a - day == date(2002, 3, 1)', ctx));
|
||||
ok(py.eval('-day + a == date(2002, 3, 1)', ctx));
|
||||
ok(py.eval('a + week == date(2002, 3, 9)', ctx));
|
||||
console.group(9)
|
||||
ok(py.eval('a - week == date(2002, 2, 23)', ctx));
|
||||
console.groupEnd();
|
||||
ok(py.eval('a + 52*week == date(2003, 3, 1)', ctx)); // 10
|
||||
ok(py.eval('a - 52*week == date(2001, 3, 3)', ctx));
|
||||
ok(py.eval('(a + week) - a == week', ctx));
|
||||
ok(py.eval('(a + day) - a == day', ctx));
|
||||
ok(py.eval('(a - week) - a == -week', ctx));
|
||||
ok(py.eval('(a - day) - a == -day', ctx)); // 15
|
||||
ok(py.eval('a - (a + week) == -week', ctx));
|
||||
ok(py.eval('a - (a + day) == -day', ctx));
|
||||
ok(py.eval('a - (a - week) == week', ctx));
|
||||
ok(py.eval('a - (a - day) == day', ctx));
|
||||
|
||||
raises(function () {
|
||||
py.eval('a + 1', ctx);
|
||||
}, /^Error: TypeError:/); // 20
|
||||
raises(function () {
|
||||
py.eval('a - 1', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
raises(function () {
|
||||
py.eval('1 + a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
raises(function () {
|
||||
py.eval('1 - a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
|
||||
// delta - date is senseless.
|
||||
raises(function () {
|
||||
py.eval('day - a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
// mixing date and (delta or date) via * or // is senseless
|
||||
raises(function () {
|
||||
py.eval('day * a', ctx);
|
||||
}, /^Error: TypeError:/); // 25
|
||||
raises(function () {
|
||||
py.eval('a * day', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
raises(function () {
|
||||
py.eval('day // a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
raises(function () {
|
||||
py.eval('a // day', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
raises(function () {
|
||||
py.eval('a * a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
raises(function () {
|
||||
py.eval('a // a', ctx);
|
||||
}, /^Error: TypeError:/); // 30
|
||||
// date + date is senseless
|
||||
raises(function () {
|
||||
py.eval('a + a', ctx);
|
||||
}, /^Error: TypeError:/);
|
||||
});
|
||||
});
|
||||
openerp.testing.section('eval.edc', {
|
||||
dependencies: ['web.data'],
|
||||
|
|
Loading…
Reference in New Issue