[MERGE] [ADD] ways to correctly handle local datetimes in e.g. domains

* Alter datetime.now(), generate a local datetime (add utcnow() which generates a UTC datetime)
* Implement datetime.replace() to manipulate local datetimes
* Implement date.today(), generates a local date
* Implement datetime.toJSON(), returns a javascript Date (assumes datetime attributes are local)
* Add conversion hook in JSON and JSONP handlers, automatically converts a Date object to a UTC datetime formatted according to server formats

Should allow the generation of correctly working (from the end-user's POV) [Today] filters, amongst other things.
Eg: a local expression in a domain for 'Today 00:00:00' would now be expressed as 'datetime.datetime.now().replace(hour=0, minute=0, second=0)' (no .strftime) and will be converted to UTC when sent to the server

bzr revid: mat@openerp.com-20140128133706-4yp610pp5w06tcia
This commit is contained in:
Martin Trigaux 2014-01-28 14:37:06 +01:00
commit 802d64ca92
3 changed files with 102 additions and 6 deletions

View File

@ -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) { openerp.jsonRpc = function(url, fct_name, params, settings) {
return genericJsonRpc(fct_name, params, function(data) { return genericJsonRpc(fct_name, params, function(data) {
return $.ajax(url, _.extend({}, settings, { return $.ajax(url, _.extend({}, settings, {
url: url, url: url,
dataType: 'json', dataType: 'json',
type: 'POST', type: 'POST',
data: JSON.stringify(data), data: JSON.stringify(data, date_to_utc),
contentType: 'application/json' contentType: 'application/json'
})); }));
}); });
@ -838,7 +858,7 @@ openerp.jsonRpc = function(url, fct_name, params, settings) {
openerp.jsonpRpc = function(url, fct_name, params, settings) { openerp.jsonpRpc = function(url, fct_name, params, settings) {
settings = settings || {}; settings = settings || {};
return genericJsonRpc(fct_name, params, function(data) { 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 payload_url = $.param({r:payload_str});
var force2step = settings.force2step || false; var force2step = settings.force2step || false;
delete settings.force2step; delete settings.force2step;

View File

@ -382,13 +382,28 @@
this[key] = asJS(args[key]); 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 () { strftime: function () {
var self = this; var self = this;
var args = py.PY_parseArgs(arguments, 'format'); var args = py.PY_parseArgs(arguments, 'format');
return py.str.fromJSON(args.format.toJSON() return py.str.fromJSON(args.format.toJSON()
.replace(/%([A-Za-z])/g, function (m, c) { .replace(/%([A-Za-z])/g, function (m, c) {
switch (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 'm': return _.str.sprintf('%02d', self.month);
case 'd': return _.str.sprintf('%02d', self.day); case 'd': return _.str.sprintf('%02d', self.day);
case 'H': return _.str.sprintf('%02d', self.hour); case 'H': return _.str.sprintf('%02d', self.hour);
@ -399,6 +414,17 @@
})); }));
}, },
now: py.classmethod.fromJSON(function () { 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(); var d = new Date();
return py.PY_call(datetime.datetime, return py.PY_call(datetime.datetime,
[d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
@ -415,7 +441,17 @@
py.PY_getAttr(args.time, 'minute'), py.PY_getAttr(args.time, 'minute'),
py.PY_getAttr(args.time, 'second') 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, { datetime.date = py.type('date', null, {
__init__: function () { __init__: function () {
@ -470,7 +506,12 @@
}, },
fromJSON: function (year, month, day) { fromJSON: function (year, month, day) {
return py.PY_call(datetime.date, [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 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 () { time.strftime = py.PY_def.fromJSON(function () {
var args = py.PY_parseArgs(arguments, 'format'); var args = py.PY_parseArgs(arguments, 'format');
var dt_class = py.PY_getAttr(datetime, 'datetime'); 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]); return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
}); });

View File

@ -265,6 +265,41 @@ openerp.testing.section('eval.types', {
instance.web.pyeval.context()), instance.web.pyeval.context()),
"2012-02-14 23:59:59"); "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', { openerp.testing.section('eval.edc', {
dependencies: ['web.data'], dependencies: ['web.data'],