summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Morel <xmo@openerp.com>2012-11-23 08:59:38 +0100
committerXavier Morel <xmo@openerp.com>2012-11-23 08:59:38 +0100
commit815b908759451efd2d74cdb8d16a46bb83fe25c0 (patch)
tree38a427d2b3d588bd261a5979dffeb3caf5053ee4
parent9105afcd68643c97235a87058d576ebdd10cad4b (diff)
[ADD] partial but sufficient (I think) implementation of datetime.timedelta
bzr revid: xmo@openerp.com-20121123075938-oazwt4bn2l639r3a
-rw-r--r--addons/web/static/src/js/pyeval.js187
-rw-r--r--addons/web/static/test/evals.js149
2 files changed, 332 insertions, 4 deletions
diff --git a/addons/web/static/src/js/pyeval.js b/addons/web/static/src/js/pyeval.js
index fe68d5652a5..e0f465279ef 100644
--- a/addons/web/static/src/js/pyeval.js
+++ b/addons/web/static/src/js/pyeval.js
@@ -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
diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js
index 2fe3168a357..ba9888617ee 100644
--- a/addons/web/static/test/evals.js
+++ b/addons/web/static/test/evals.js
@@ -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'],