diff --git a/addons/web/models/ir_http.py b/addons/web/models/ir_http.py index 73a83664683..a1fa58ef81d 100644 --- a/addons/web/models/ir_http.py +++ b/addons/web/models/ir_http.py @@ -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(), diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js index c98ff7fbe9a..033d7995c9c 100644 --- a/addons/web/static/src/js/core/session.js +++ b/addons/web/static/src/js/core/session.js @@ -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; diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index ec04f67e270..035d24dbdf2 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -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; }, diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index 787f66080dd..db5cd34d8b9 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -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()) { diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index d2b0c02c524..ed02983d957 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -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'); diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index a1aa5958009..35fa05a8eab 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -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; + }, }, }); diff --git a/addons/web/static/tests/fields/field_utils_tests.js b/addons/web/static/tests/fields/field_utils_tests.js index 922c9f3a79c..a0284798553 100644 --- a/addons/web/static/tests/fields/field_utils_tests.js +++ b/addons/web/static/tests/fields/field_utils_tests.js @@ -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); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index fd872fe2ca7..3d6721a55df 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1771,7 +1771,9 @@ QUnit.module('relational_fields', { '', res_id: 1, session: { - tzOffset: 120 + getTZOffset: function () { + return 120; + }, }, }); assert.strictEqual(form.$('td:eq(0)').text(), "01/25/2017", diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 56070453e31..0115dfc9152 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -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]; diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 6832c71b170..29e76182bb4 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -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; + }, }, }); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 3a1976592d7..ada05e5ed28 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -2849,7 +2849,9 @@ QUnit.module('Views', { '', res_id: 1, session: { - tzOffset: 120 + getTZOffset: function () { + return 120; + }, }, }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 402b59194f8..083aa349516 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -1271,7 +1271,9 @@ QUnit.module('Views', { data: this.data, arch: '', session: { - tzOffset: 120 + getTZOffset: function () { + return 120; + }, }, });