[FIX] web: always use browser timezone

With the new views, we stopped using the browser timezone to
display the dates in Odoo, and we used the timezone defined on
the User profile instead. When loading the webclient, the
timezone offset was put into the session and used to display all
dates. This wasn't a good idea.

The given offset was computed for the current time, meaning that
it may be incorrect for specific dates (e.g. with the daylight
saving, the UTC offset of today is not the same as 6 months ago).
Moreover (but less likely), as the offset was stored in the
session, it wasn't recalculated afterwards. So if the offset
actually changed during the session (e.g. from or to daylight
saving time), the displayed dates were incorrect until the user
reloaded the page.

With this rev., we don't retrieve the offset from the server
anymore and we use the browser timezone again (like before the new
views). However, we keep the computation of the offset (on the fly)
in the session, so that it can be mocked in the test environment.
This commit is contained in:
Adrien Dieudonne
2017-08-17 17:24:00 +02:00
committed by Aaron Bohy
parent b9bfec96dd
commit f5b7ad80fc
12 changed files with 99 additions and 36 deletions
-3
View File
@@ -5,8 +5,6 @@ import json
from odoo import models
from odoo.http import request
import pytz
import datetime
import odoo
@@ -26,7 +24,6 @@ class Http(models.AbstractModel):
version_info = odoo.service.common.exp_version()
return {
"session_id": request.session.sid,
"tzOffset": self._context.get('tz') and datetime.datetime.now(pytz.timezone(self._context['tz'])).utcoffset().total_seconds() / 60 or False,
"uid": request.session.uid,
"is_system": request.env.user._is_system(),
"is_superuser": request.env.user._is_superuser(),
+11 -4
View File
@@ -285,10 +285,6 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
var result = _.extend({}, window.odoo.session_info);
delete result.session_id;
_.extend(this, result);
if (this.tzOffset === false) {
// send by server to have the user timezone and not the browser timezone
this.tzOffset = -new Date().getTimezoneOffset();
}
return $.when();
},
check_session_id: function () {
@@ -410,6 +406,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
}) ? '' : this.prefix;
return prefix + path + qs;
},
/**
* Returns the time zone difference (in minutes) from the current locale
* (host system settings) to UTC, for a given date. The offset is positive
* if the local timezone is behind UTC, and negative if it is ahead.
*
* @param {string || moment} date a valid string date or moment instance
* @returns {integer}
*/
getTZOffset: function (date) {
return -new Date(date).getTimezoneOffset();
},
});
return Session;
@@ -450,7 +450,7 @@ var FieldDateTime = FieldDate.extend({
*/
_getValue: function () {
var value = this.datewidget.getValue();
return value && value.add(-this.getSession().tzOffset, 'minutes');
return value && value.add(-this.getSession().getTZOffset(value), 'minutes');
},
/**
@@ -460,7 +460,7 @@ var FieldDateTime = FieldDate.extend({
* @private
*/
_makeDatePicker: function () {
var value = this.value && this.value.clone().add(this.getSession().tzOffset, 'minutes');
var value = this.value && this.value.clone().add(this.getSession().getTZOffset(this.value), 'minutes');
return new datepicker.DateTimeWidget(this, {defaultDate: value});
},
@@ -471,7 +471,7 @@ var FieldDateTime = FieldDate.extend({
* @private
*/
_renderEdit: function () {
var value = this.value && this.value.clone().add(this.getSession().tzOffset, 'minutes');
var value = this.value && this.value.clone().add(this.getSession().getTZOffset(this.value), 'minutes');
this.datewidget.setValue(value);
this.$input = this.datewidget.$input;
},
@@ -97,7 +97,7 @@ function formatDate(value, field, options) {
}
if (field && field.type === 'datetime') {
if (!options || !('timezone' in options) || options.timezone) {
value = value.clone().add(session.tzOffset, 'minutes');
value = value.clone().add(session.getTZOffset(value), 'minutes');
}
}
var l10n = core._t.database.parameters;
@@ -123,7 +123,7 @@ function formatDateTime(value, field, options) {
return "";
}
if (!options || !('timezone' in options) || options.timezone) {
value = value.clone().add(session.tzOffset, 'minutes');
value = value.clone().add(session.getTZOffset(value), 'minutes');
}
var l10n = core._t.database.parameters;
var date_format = time.strftime_to_moment_format(l10n.date_format);
@@ -404,7 +404,7 @@ function parseDateTime(value, field, options) {
} else {
datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601], true);
if (options && options.timezone) {
datetime.add(-session.tzOffset, 'minutes');
datetime.add(-session.getTZOffset(datetime), 'minutes');
}
}
if (datetime.isValid()) {
@@ -65,12 +65,12 @@ return AbstractModel.extend({
end.startOf('day').add(-1, 'days');
} else {
// default hours in the user's timezone
start.hours(7).add(-this.getSession().tzOffset, 'minutes');
end.hours(19).add(-this.getSession().tzOffset, 'minutes');
start.hours(7).add(-this.getSession().getTZOffset(start), 'minutes');
end.hours(19).add(-this.getSession().getTZOffset(end), 'minutes');
}
} else {
start.add(-this.getSession().tzOffset, 'minutes');
end.add(-this.getSession().tzOffset, 'minutes');
start.add(-this.getSession().getTZOffset(start), 'minutes');
end.add(-this.getSession().getTZOffset(end), 'minutes');
}
if (this.mapping.all_day) {
@@ -599,8 +599,8 @@ return AbstractModel.extend({
date_stop = date_start.clone().add(date_delay,'hours');
}
date_start.add(this.getSession().tzOffset, 'minutes');
date_stop.add(this.getSession().tzOffset, 'minutes');
date_start.add(this.getSession().getTZOffset(date_start), 'minutes');
date_stop.add(this.getSession().getTZOffset(date_stop), 'minutes');
if (this.mapping.all_day && evt[this.mapping.all_day]) {
date_stop.add(1, 'days');
@@ -1589,7 +1589,9 @@ QUnit.module('basic_fields', {
date_format: '%m/%d/%Y',
},
session: {
tzOffset: 120 // Should be ignored by date fields
getTZOffset: function () {
return 120; // Should be ignored by date fields
},
},
});
@@ -1634,7 +1636,9 @@ QUnit.module('basic_fields', {
date_format: '%m/%d/%Y',
},
session: {
tzOffset: -120 // Should be ignored by date fields
getTZOffset: function () {
return -120; // Should be ignored by date fields
},
},
});
@@ -1663,7 +1667,9 @@ QUnit.module('basic_fields', {
date_format: '%m/%d/%Y',
},
session: {
tzOffset: 0
getTZOffset: function () {
return 0;
},
},
});
@@ -1752,7 +1758,9 @@ QUnit.module('basic_fields', {
time_format: '%H:%M:%S',
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
@@ -1809,7 +1817,9 @@ QUnit.module('basic_fields', {
time_format: '%H:%M:%S',
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
@@ -1879,7 +1889,9 @@ QUnit.module('basic_fields', {
time_format: '%H:%M:%S',
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
@@ -1924,7 +1936,9 @@ QUnit.module('basic_fields', {
time_format: '%H:%M:%S',
},
session: {
tzOffset: -240,
getTZOffset: function () {
return -240;
},
},
});
@@ -1966,7 +1980,9 @@ QUnit.module('basic_fields', {
time_format: '%H:%M:%S',
},
session: {
tzOffset: -240,
getTZOffset: function () {
return -240;
},
},
});
@@ -4,6 +4,7 @@ odoo.define('web.field_utils_tests', function (require) {
var core = require('web.core');
var session = require('web.session');
var fieldUtils = require('web.field_utils');
var testUtils = require('web.test_utils');
QUnit.module('fields', {}, function () {
@@ -63,6 +64,31 @@ QUnit.test("format_datetime", function (assert) {
assert.strictEqual(str, moment(date).format("MM/DD/YYYY HH:mm:ss"));
});
QUnit.test("format_datetime (with different timezone offset)", function (assert) {
assert.expect(2);
// mock the date format to avoid issues due to localisation
var dateFormat = core._t.database.parameters.date_format;
core._t.database.parameters.date_format = '%m/%d/%Y';
session.getTZOffset = function (date) {
// simulate daylight saving time
var startDate = new Date(2017, 2, 26);
var endDate = new Date(2017, 9, 29);
if (startDate < date && date < endDate) {
return 120; // UTC+2
} else {
return 60; // UTC+1
}
};
var str = fieldUtils.format.datetime(moment.utc('2017-01-01T10:00:00Z'));
assert.strictEqual(str, '01/01/2017 11:00:00');
str = fieldUtils.format.datetime(moment.utc('2017-06-01T10:00:00Z'));
assert.strictEqual(str, '06/01/2017 12:00:00');
core._t.database.parameters.date_format = dateFormat;
});
QUnit.test("format_many2one", function (assert) {
assert.expect(2);
@@ -1771,7 +1771,9 @@ QUnit.module('relational_fields', {
'</form>',
res_id: 1,
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
assert.strictEqual(form.$('td:eq(0)').text(), "01/25/2017",
@@ -256,8 +256,11 @@ function addMockEnvironment(widget, params) {
var initialDebounce = DebouncedField.prototype.DEBOUNCE;
DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0;
var initialSession, initialConfig, initialParameters;
initialSession = _.extend({}, session);
session.getTZOffset = function () {
return 0; // by default, but may be overriden in specific tests
};
if ('session' in params) {
initialSession = _.extend({}, session);
_.extend(session, params.session);
}
if ('config' in params) {
@@ -282,8 +285,8 @@ function addMockEnvironment(widget, params) {
for (key in session) {
delete session[key];
}
_.extend(session, initialSession);
}
_.extend(session, initialSession);
if ('config' in params) {
for (key in config) {
delete config[key];
@@ -440,7 +440,9 @@ QUnit.module('Views', {
initialDate: initialDate,
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
mockRPC: function (route, args) {
if (args.method === "create") {
@@ -541,7 +543,9 @@ QUnit.module('Views', {
initialDate: initialDate,
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
mockRPC: function (route, args) {
if (args.method === "create") {
@@ -664,7 +668,9 @@ QUnit.module('Views', {
initialDate: initialDate,
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
@@ -728,7 +734,9 @@ QUnit.module('Views', {
initialDate: initialDate,
},
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
+3 -1
View File
@@ -2849,7 +2849,9 @@ QUnit.module('Views', {
'</form>',
res_id: 1,
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});
+3 -1
View File
@@ -1271,7 +1271,9 @@ QUnit.module('Views', {
data: this.data,
arch: '<tree><field name="date"/><field name="datetime"/></tree>',
session: {
tzOffset: 120
getTZOffset: function () {
return 120;
},
},
});