[ADD] partial but sufficient (I think) implementation of datetime.timedelta

bzr revid: xmo@openerp.com-20121123075938-oazwt4bn2l639r3a
This commit is contained in:
Xavier Morel 2012-11-23 08:59:38 +01:00
parent 9105afcd68
commit 815b908759
2 changed files with 332 additions and 4 deletions

View File

@ -14,6 +14,188 @@ openerp.web.pyeval = function (instance) {
};
var datetime = py.PY_call(py.object);
/**
* computes (Math.floor(a/b), a%b and passes that to the callback.
*
* returns the callback's result
*/
var divmod = function (a, b, fn) {
var mod = a%b;
// in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a
// round of b
if (mod > 0 && b < 0 || mod < 0 && b > 0) {
mod += b;
}
return fn(Math.floor(a/b), mod);
};
/**
* Passes the fractional and integer parts of x to the callback, returns
* the callback's result
*/
var modf = function (x, fn) {
var mod = x%1;
if (mod < 0) {
mod += 1;
}
return fn(mod, Math.floor(x));
};
var zero = py.float.fromJSON(0);
// Port from pypy/lib_pypy/datetime.py
datetime.timedelta = py.type('timedelta', null, {
__init__: function () {
var args = py.PY_parseArgs(arguments, [
['days', zero], ['seconds', zero], ['microseconds', zero],
['milliseconds', zero], ['minutes', zero], ['hours', zero],
['weeks', zero]
]);
var d = 0, s = 0, m = 0;
var days = args.days.toJSON() + args.weeks.toJSON() * 7;
var seconds = args.seconds.toJSON()
+ args.minutes.toJSON() * 60
+ args.hours.toJSON() * 3600;
var microseconds = args.microseconds.toJSON()
+ args.milliseconds.toJSON() * 1000;
// Get rid of all fractions, and normalize s and us.
// Take a deep breath <wink>.
var daysecondsfrac = modf(days, function (dayfrac, days) {
d = days;
if (dayfrac) {
return modf(dayfrac * 24 * 3600, function (dsf, dsw) {
s = dsw;
return dsf;
});
}
return 0;
});
var secondsfrac = modf(seconds, function (sf, s) {
seconds = s;
return sf + daysecondsfrac;
});
divmod(seconds, 24*3600, function (days, seconds) {
d += days;
s += seconds
});
// seconds isn't referenced again before redefinition
microseconds += secondsfrac * 1e6;
divmod(microseconds, 1000000, function (seconds, microseconds) {
divmod(seconds, 24*3600, function (days, seconds) {
d += days;
s += seconds;
m += Math.round(microseconds);
});
});
// Carrying still possible here?
this.days = d;
this.seconds = s;
this.microseconds = m;
},
__str__: function () {
var hh, mm, ss;
divmod(this.seconds, 60, function (m, s) {
divmod(m, 60, function (h, m) {
hh = h;
mm = m;
ss = s;
});
});
var s = _.str.sprintf("%d:%02d:%02d", hh, mm, ss);
if (this.days) {
s = _.str.sprintf("%d day%s, %s",
this.days,
(this.days != 1 && this.days != -1) ? 's' : '',
s);
}
if (this.microseconds) {
s = _.str.sprintf("%s.%06d", s, this.microseconds);
}
return py.str.fromJSON(s);
},
__eq__: function (other) {
if (py.PY_call(py.isinstance, [other, datetime.timedelta]) !== py.True) {
return py.False;
}
return (this.days === other.days
&& this.seconds === other.seconds
&& this.microseconds === other.microseconds)
? py.True : py.False;
},
__add__: function (other) {
if (py.PY_call(py.isinstance, [other, datetime.timedelta]) !== py.True) {
return py.NotImplemented;
}
return py.PY_call(datetime.timedelta, [
py.float.fromJSON(this.days + other.days),
py.float.fromJSON(this.seconds + other.seconds),
py.float.fromJSON(this.microseconds + other.microseconds)
]);
},
__radd__: function (other) { return this.__add__(other); },
__sub__: function (other) {
if (py.PY_call(py.isinstance, [other, datetime.timedelta]) !== py.True) {
return py.NotImplemented;
}
return py.PY_call(datetime.timedelta, [
py.float.fromJSON(this.days - other.days),
py.float.fromJSON(this.seconds - other.seconds),
py.float.fromJSON(this.microseconds - other.microseconds)
]);
},
__rsub__: function (other) {
if (py.PY_call(py.isinstance, [other, datetime.timedelta]) !== py.True) {
return py.NotImplemented;
}
return this.__neg__().__add__(other);
},
__neg__: function () {
return py.PY_call(datetime.timedelta, [
py.float.fromJSON(-this.days),
py.float.fromJSON(-this.seconds),
py.float.fromJSON(-this.microseconds)
]);
},
__pos__: function () { return this; },
__mul__: function (other) {
if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
return py.NotImplemented;
}
var n = other.toJSON();
return py.PY_call(datetime.timedelta, [
py.float.fromJSON(this.days * n),
py.float.fromJSON(this.seconds * n),
py.float.fromJSON(this.microseconds * n)
]);
},
__rmul__: function (other) { return this.__mul__(other); },
__div__: function (other) {
if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
return py.NotImplemented;
}
var usec = ((this.days * 24 * 3600) + this.seconds) * 1000000
+ this.microseconds;
return py.PY_call(
datetime.timedelta, [
zero, zero, py.float.fromJSON(usec / other.toJSON())]);
},
__floordiv__: function (other) { return this.__div__(other); },
total_seconds: function () {
return py.float.fromJSON(
this.days * 86400
+ this.seconds
+ this.microseconds / 1000000)
},
__nonzero__: function () {
return (!!this.days || !!this.seconds || !!this.microseconds)
? py.True
: py.False;
}
});
datetime.datetime = py.type('datetime', null, {
__init__: function () {
var zero = py.float.fromJSON(0);
@ -108,7 +290,6 @@ openerp.web.pyeval = function (instance) {
}
}
});
var time = py.PY_call(py.object);
time.strftime = py.PY_def.fromJSON(function () {
// FIXME: needs PY_getattr
@ -127,7 +308,7 @@ openerp.web.pyeval = function (instance) {
+ 'weekday leakdays yearday nlyearday');
},
__add__: function (other) {
if (py.PY_call(py.isinstance, [datetime.date]) !== py.True) {
if (py.PY_call(py.isinstance, [other, datetime.date]) !== py.True) {
return py.NotImplemented;
}
// TODO: test this whole mess
@ -172,7 +353,7 @@ openerp.web.pyeval = function (instance) {
},
__sub__: function (other) {
if (py.PY_call(py.isinstance, [datetime.date]) !== py.True) {
if (py.PY_call(py.isinstance, [other, datetime.date]) !== py.True) {
return py.NotImplemented;
}
// TODO: test this whole mess

View File

@ -1,5 +1,8 @@
openerp.testing.section('eval.types', {
dependencies: ['web.coresetup']
dependencies: ['web.coresetup'],
setup: function (instance) {
instance.session.uid = 42;
}
}, function (test) {
test('strftime', function (instance) {
var d = new Date();
@ -16,6 +19,150 @@ openerp.testing.section('eval.types', {
d.getFullYear(), d.getMonth() + 1, d.getDate(),
d.getHours(), d.getMinutes(), d.getSeconds()));
});
// Port from pypy/lib_pypy/test_datetime.py
var makeEq = function (instance, c2) {
var ctx = instance.web.pyeval.context();
var c = _.extend({ td: ctx.datetime.timedelta }, c2 || {});
return function (a, b, message) {
ok(py.eval(a + ' == ' + b, c), message);
};
};
test('timedelta.test_constructor', function (instance) {
var eq = makeEq(instance);
// keyword args to constructor
eq('td()', 'td(weeks=0, days=0, hours=0, minutes=0, seconds=0, ' +
'milliseconds=0, microseconds=0)');
eq('td(1)', 'td(days=1)');
eq('td(0, 1)', 'td(seconds=1)');
eq('td(0, 0, 1)', 'td(microseconds=1)');
eq('td(weeks=1)', 'td(days=7)');
eq('td(days=1)', 'td(hours=24)');
eq('td(hours=1)', 'td(minutes=60)');
eq('td(minutes=1)', 'td(seconds=60)');
eq('td(seconds=1)', 'td(milliseconds=1000)');
eq('td(milliseconds=1)', 'td(microseconds=1000)');
// Check float args to constructor
eq('td(weeks=1.0/7)', 'td(days=1)');
eq('td(days=1.0/24)', 'td(hours=1)');
eq('td(hours=1.0/60)', 'td(minutes=1)');
eq('td(minutes=1.0/60)', 'td(seconds=1)');
eq('td(seconds=0.001)', 'td(milliseconds=1)');
eq('td(milliseconds=0.001)', 'td(microseconds=1)');
});
test('timedelta.test_computations', function (instance) {
var c = instance.web.pyeval.context();
var zero = py.float.fromJSON(0);
var eq = makeEq(instance, {
// one week
a: py.PY_call(c.datetime.timedelta, [
py.float.fromJSON(7)]),
// one minute
b: py.PY_call(c.datetime.timedelta, [
zero, py.float.fromJSON(60)]),
// one millisecond
c: py.PY_call(c.datetime.timedelta, [
zero, zero, py.float.fromJSON(1000)]),
});
eq('a+b+c', 'td(7, 60, 1000)');
eq('a-b', 'td(6, 24*3600 - 60)');
eq('-a', 'td(-7)');
eq('+a', 'td(7)');
eq('-b', 'td(-1, 24*3600 - 60)');
eq('-c', 'td(-1, 24*3600 - 1, 999000)');
// eq('abs(a)', 'a');
// eq('abs(-a)', 'a');
eq('td(6, 24*3600)', 'a');
eq('td(0, 0, 60*1000000)', 'b');
eq('a*10', 'td(70)');
eq('a*10', '10*a');
// eq('a*10L', '10*a');
eq('b*10', 'td(0, 600)');
eq('10*b', 'td(0, 600)');
// eq('b*10L', 'td(0, 600)');
eq('c*10', 'td(0, 0, 10000)');
eq('10*c', 'td(0, 0, 10000)');
// eq('c*10L', 'td(0, 0, 10000)');
eq('a*-1', '-a');
eq('b*-2', '-b-b');
eq('c*-2', '-c+-c');
eq('b*(60*24)', '(b*60)*24');
eq('b*(60*24)', '(60*b)*24');
eq('c*1000', 'td(0, 1)');
eq('1000*c', 'td(0, 1)');
eq('a//7', 'td(1)');
eq('b//10', 'td(0, 6)');
eq('c//1000', 'td(0, 0, 1)');
eq('a//10', 'td(0, 7*24*360)');
eq('a//3600000', 'td(0, 0, 7*24*1000)');
// Issue #11576
eq('td(999999999, 86399, 999999) - td(999999999, 86399, 999998)', 'td(0, 0, 1)');
eq('td(999999999, 1, 1) - td(999999999, 1, 0)',
'td(0, 0, 1)')
});
test('timedelta.test_basic_attributes', function (instance) {
var ctx = instance.web.pyeval.context();
strictEqual(py.eval('datetime.timedelta(1, 7, 31).days', ctx), 1);
strictEqual(py.eval('datetime.timedelta(1, 7, 31).seconds', ctx), 7);
strictEqual(py.eval('datetime.timedelta(1, 7, 31).microseconds', ctx), 31);
});
test('timedelta.test_total_seconds', function (instance) {
var c = { timedelta: instance.web.pyeval.context().datetime.timedelta };
strictEqual(py.eval('timedelta(365).total_seconds()', c), 31536000);
strictEqual(
py.eval('timedelta(seconds=123456.789012).total_seconds()', c),
123456.789012);
strictEqual(
py.eval('timedelta(seconds=-123456.789012).total_seconds()', c),
-123456.789012);
strictEqual(
py.eval('timedelta(seconds=0.123456).total_seconds()', c), 0.123456);
strictEqual(py.eval('timedelta().total_seconds()', c), 0);
strictEqual(
py.eval('timedelta(seconds=1000000).total_seconds()', c), 1e6);
});
test('timedelta.test_str', function (instance) {
var c = { td: instance.web.pyeval.context().datetime.timedelta };
strictEqual(py.eval('str(td(1))', c), "1 day, 0:00:00");
strictEqual(py.eval('str(td(-1))', c), "-1 day, 0:00:00");
strictEqual(py.eval('str(td(2))', c), "2 days, 0:00:00");
strictEqual(py.eval('str(td(-2))', c), "-2 days, 0:00:00");
strictEqual(py.eval('str(td(hours=12, minutes=58, seconds=59))', c),
"12:58:59");
strictEqual(py.eval('str(td(hours=2, minutes=3, seconds=4))', c),
"2:03:04");
strictEqual(
py.eval('str(td(weeks=-30, hours=23, minutes=12, seconds=34))', c),
"-210 days, 23:12:34");
strictEqual(py.eval('str(td(milliseconds=1))', c), "0:00:00.001000");
strictEqual(py.eval('str(td(microseconds=3))', c), "0:00:00.000003");
strictEqual(
py.eval('str(td(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999))', c),
"999999999 days, 23:59:59.999999");
});
test('timedelta.test_massive_normalization', function (instance) {
var td = py.PY_call(
instance.web.pyeval.context().datetime.timedelta,
{microseconds: py.float.fromJSON(-1)});
strictEqual(td.days, -1);
strictEqual(td.seconds, 24 * 3600 - 1);
strictEqual(td.microseconds, 999999);
});
test('timedelta.test_bool', function (instance) {
var c = { td: instance.web.pyeval.context().datetime.timedelta };
ok(py.eval('bool(td(1))', c));
ok(py.eval('bool(td(0, 1))', c));
ok(py.eval('bool(td(0, 0, 1))', c));
ok(py.eval('bool(td(microseconds=1))', c));
ok(py.eval('bool(not td(0))', c));
});
});
openerp.testing.section('eval.edc', {
dependencies: ['web.data'],