From 36eedfab43a28b72a6dd6375c0f71828f238fc37 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 22 Jan 2014 14:54:26 +0100 Subject: [PATCH 1/2] [ADD] JSONify Date objects to a UTC datetime in OpenERP server format This way, the client can send genuine local Date objects without caring too much, the server will get them in an expected format. This can then be used by leaving Date objects in domains, and have these date objects end up correctly in the server without creators of domains having to manually serialize them to UTC from a local datetime somehow. bzr revid: xmo@openerp.com-20140122135426-zm80cj5wm9iebs76 --- addons/web/static/src/js/openerpframework.js | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index 22ca70ebbf7..71f45899f5f 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -823,13 +823,33 @@ var genericJsonRpc = function(fct_name, params, fct) { }); }; +/** + * Replacer function for JSON.stringify, serializes Date objects to UTC + * datetime in the OpenERP Server format. + * + * However, if a serialized value has a toJSON method that method is called + * *before* the replacer is invoked. Date#toJSON exists, and thus the value + * passed to the replacer is a string, the original Date has to be fetched + * on the parent object (which is provided as the replacer's context). + * + * @param {String} k + * @param {Object} v + * @returns {Object} + */ +function date_to_utc(k, v) { + var value = this[k]; + if (!(value instanceof Date)) { return v; } + + return openerp.datetime_to_str(value); +} + openerp.jsonRpc = function(url, fct_name, params, settings) { return genericJsonRpc(fct_name, params, function(data) { return $.ajax(url, _.extend({}, settings, { url: url, dataType: 'json', type: 'POST', - data: JSON.stringify(data), + data: JSON.stringify(data, date_to_utc), contentType: 'application/json' })); }); @@ -838,7 +858,7 @@ openerp.jsonRpc = function(url, fct_name, params, settings) { openerp.jsonpRpc = function(url, fct_name, params, settings) { settings = settings || {}; return genericJsonRpc(fct_name, params, function(data) { - var payload_str = JSON.stringify(data); + var payload_str = JSON.stringify(data, date_to_utc); var payload_url = $.param({r:payload_str}); var force2step = settings.force2step || false; delete settings.force2step; From 5b7ade9db180b8a038c2a9944aba85d1ffd220ea Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 22 Jan 2014 16:19:11 +0100 Subject: [PATCH 2/2] [ADD] ways to correctly handle local datetimes in e.g. domains * change datetime.now() to generate user-local naive datetimes * add datetime.utcnow() behaving as the old now() * add date.today() generating a user-local date (for the current day) * add datetime.replace() to replace any specific attribute of the datetime object (except tzinfo, for now) * datetime.toJSON() now returns the equivalent javascript Date object (warning: uses the datetime attributes directly, since datetimes are naive if they were created with utcnow() the Date result is going to be complete nonsense). With the previous commit datetime.now() generates a user-local now() which is converted to the correct UTC datetime when sent to the server. This means it becomes possible to generate datetime bounds for the user's local today with either datetime.datetime.now().replace(hour=0, minute=0, second=0) or datetime.datetime.combine( datetime.date.today(), datetime.time()) and once send over JSON-RPC the server will get the local datetime in UTC to the server's format. nb: user-local means "in the timezone of the user's browser" in this context. bzr revid: xmo@openerp.com-20140122151911-akn1nr6e739eg92s --- addons/web/static/src/js/pyeval.js | 49 +++++++++++++++++++++++++++--- addons/web/static/test/evals.js | 35 +++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/js/pyeval.js b/addons/web/static/src/js/pyeval.js index 0b0e9725af9..7da0f3d6f32 100644 --- a/addons/web/static/src/js/pyeval.js +++ b/addons/web/static/src/js/pyeval.js @@ -382,13 +382,28 @@ this[key] = asJS(args[key]); } }, + replace: function () { + var args = py.PY_parseArgs(arguments, [ + ['year', py.None], ['month', py.None], ['day', py.None], + ['hour', py.None], ['minute', py.None], ['second', py.None], + ['microsecond', py.None] // FIXME: tzinfo, can't use None as valid input + ]); + var params = {}; + for(var key in args) { + if (!args.hasOwnProperty(key)) { continue; } + + var arg = args[key]; + params[key] = (arg === py.None ? this[key] : asJS(arg)); + } + return py.PY_call(datetime.datetime, params); + }, strftime: function () { var self = this; var args = py.PY_parseArgs(arguments, 'format'); return py.str.fromJSON(args.format.toJSON() .replace(/%([A-Za-z])/g, function (m, c) { switch (c) { - case 'Y': return self.year; + case 'Y': return _.str.sprintf('%04d', self.year); case 'm': return _.str.sprintf('%02d', self.month); case 'd': return _.str.sprintf('%02d', self.day); case 'H': return _.str.sprintf('%02d', self.hour); @@ -399,6 +414,17 @@ })); }, now: py.classmethod.fromJSON(function () { + var d = new Date; + return py.PY_call(datetime.datetime, [ + d.getFullYear(), d.getMonth() + 1, d.getDate(), + d.getHours(), d.getMinutes(), d.getSeconds(), + d.getMilliseconds() * 1000]); + }), + today: py.classmethod.fromJSON(function () { + var dt_class = py.PY_getAttr(datetime, 'datetime'); + return py.PY_call(py.PY_getAttr(dt_class, 'now')); + }), + utcnow: py.classmethod.fromJSON(function () { var d = new Date(); return py.PY_call(datetime.datetime, [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), @@ -415,7 +441,17 @@ py.PY_getAttr(args.time, 'minute'), py.PY_getAttr(args.time, 'second') ]); - }) + }), + toJSON: function () { + return new Date( + this.year, + this.month - 1, + this.day, + this.hour, + this.minute, + this.second, + this.microsecond / 1000); + }, }); datetime.date = py.type('date', null, { __init__: function () { @@ -470,7 +506,12 @@ }, fromJSON: function (year, month, day) { return py.PY_call(datetime.date, [year, month, day]); - } + }, + today: py.classmethod.fromJSON(function () { + var d = new Date; + return py.PY_call(datetime.date, [ + d.getFullYear(), d.getMonth() + 1, d.getDate()]); + }), }); /** Returns the current local date, which means the date on the client (which can be different @@ -501,7 +542,7 @@ time.strftime = py.PY_def.fromJSON(function () { var args = py.PY_parseArgs(arguments, 'format'); var dt_class = py.PY_getAttr(datetime, 'datetime'); - var d = py.PY_call(py.PY_getAttr(dt_class, 'now')); + var d = py.PY_call(py.PY_getAttr(dt_class, 'utcnow')); return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]); }); diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js index 840ad051aca..13ac0f3f3b7 100644 --- a/addons/web/static/test/evals.js +++ b/addons/web/static/test/evals.js @@ -265,6 +265,41 @@ openerp.testing.section('eval.types', { instance.web.pyeval.context()), "2012-02-14 23:59:59"); }); + test('datetime.tojson', function (instance) { + var result = py.eval( + 'datetime.datetime(2012, 2, 15, 1, 7, 31)', + instance.web.pyeval.context()); + ok(result instanceof Date); + equal(result.getFullYear(), 2012); + equal(result.getMonth(), 1); + equal(result.getDate(), 15); + equal(result.getHours(), 1); + equal(result.getMinutes(), 7); + equal(result.getSeconds(), 31); + }); + test('datetime.combine', function (instance) { + var result = py.eval( + 'datetime.datetime.combine(datetime.date(2012, 2, 15),' + + ' datetime.time(1, 7, 13))' + + ' .strftime("%Y-%m-%d %H:%M:%S")', + instance.web.pyeval.context()); + equal(result, "2012-02-15 01:07:13"); + + result = py.eval( + 'datetime.datetime.combine(datetime.date(2012, 2, 15),' + + ' datetime.time())' + + ' .strftime("%Y-%m-%d %H:%M:%S")', + instance.web.pyeval.context()); + equal(result, '2012-02-15 00:00:00'); + }); + test('datetime.replace', function (instance) { + var result = py.eval( + 'datetime.datetime(2012, 2, 15, 1, 7, 13)' + + ' .replace(hour=0, minute=0, second=0)' + + ' .strftime("%Y-%m-%d %H:%M:%S")', + instance.web.pyeval.context()); + equal(result, "2012-02-15 00:00:00"); + }); }); openerp.testing.section('eval.edc', { dependencies: ['web.data'],