From c346428397d257881969978317f950fdb81e925c Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 9 Nov 2018 16:37:26 +0000 Subject: [PATCH 1/4] [FIX] web: search input date in another timezone Put the browser in timezone Sao Paulo (Or like West of UTC) Trigger the autocomplete (and the subsequent search) on a date field by writing a date (in the locale format) in the search view Before this commit, the domain sent to the server contained the date as the day **before** the one asked for in the input This is because, the string input is parsed and gives: input = 12/02/2018 When creating the moment object: > since there is no explicit time, moment will interpret it as 12am (midnight) > We force moment to consider the string as being UTC (function: moment.utc()) > the moment contains, as output the time ** 2018-02-12T00:00:00 UTC ** When getting the facet value for making the domain > we call toDate on the moment object, which, according to the browser is UTC and will be converted to the locale timezone before formatting > And the domain will use the date 2018-02-11T22:00:00 Brazil/SaoPaulo which gives the day before the one we asked for After this commit, this issue doesn't arise, because we use the string representation of the moment object in search_inputs (i.e 2018-02-12T00:00:00 UTC), to create a new one *BUT* we create it with a date format that excludes the time from being interpreted. Also, the resulting moment object in this case is not flagged as UTC anymore OPW 1903224 --- addons/web/static/src/js/chrome/search_inputs.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js index a2923d1a607..6d8d4ec35b3 100644 --- a/addons/web/static/src/js/chrome/search_inputs.js +++ b/addons/web/static/src/js/chrome/search_inputs.js @@ -354,6 +354,13 @@ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ return $.when(null); } + if (t === 'date') { + // v is flagged UTC, so it will get transformed + // into the browser's timezone when calling toDate + // to avoid it, we use the string representation + // that will be truncated later to build another moment + v = v.format(); + } var m = moment(v, t === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); if (!m.isValid()) { return $.when(null); } var date_string = field_utils.format[t](m, {type: t}); From bd83517fecaaa6803fd6e300085b98470242bc8d Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Mon, 12 Nov 2018 15:29:07 +0000 Subject: [PATCH 2/4] [FIXUP] testable solution --- addons/web/static/src/js/chrome/search_inputs.js | 12 +++--------- addons/web/static/src/js/fields/field_utils.js | 10 +++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js index 6d8d4ec35b3..29db17df252 100644 --- a/addons/web/static/src/js/chrome/search_inputs.js +++ b/addons/web/static/src/js/chrome/search_inputs.js @@ -346,24 +346,18 @@ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ complete: function (needle) { // Make sure the needle has a correct format before the creation of the moment object. See // issue https://github.com/moment/moment/issues/1407 + var parseFormatOptions = {timezone: true}; var t, v; try { t = (this.attrs && this.attrs.type === 'datetime') ? 'datetime' : 'date'; - v = field_utils.parse[t](needle, {type: t}, {timezone: true}); + v = field_utils.parse[t](needle, {type: t}, parseFormatOptions); } catch (e) { return $.when(null); } - if (t === 'date') { - // v is flagged UTC, so it will get transformed - // into the browser's timezone when calling toDate - // to avoid it, we use the string representation - // that will be truncated later to build another moment - v = v.format(); - } var m = moment(v, t === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); if (!m.isValid()) { return $.when(null); } - var date_string = field_utils.format[t](m, {type: t}); + var date_string = field_utils.format[t](m, {type: t}, parseFormatOptions); var label = _.str.sprintf(_.str.escapeHTML( _t("Search %(field)s at: %(value)s")), { field: '' + _.escape(this.attrs.string) + '', diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index 5a11f1b0ba8..7da03b023b8 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -109,10 +109,11 @@ function formatDate(value, field, options) { if (value === false) { return ""; } - if (field && field.type === 'datetime') { - if (!options || !('timezone' in options) || options.timezone) { + var fieldType = field && field.type; + if (fieldType === 'datetime' && + (!options || !('timezone' in options)) || + options && options.timezone) { value = value.clone().add(session.getTZOffset(value), 'minutes'); - } } var date_format = time.getLangDateFormat(); return value.format(date_format); @@ -372,6 +373,9 @@ function parseDate(value, field, options) { date = moment.utc(value); } else { date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601], true); + if (options && options.timezone) { + date.add(-session.getTZOffset(date), 'minutes'); + } } if (date.isValid()) { if (date.year() === 0) { From 293f9d27ad8cf2d9efddf20467264c46d324d84d Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Mon, 12 Nov 2018 16:12:29 +0000 Subject: [PATCH 3/4] [WIP] another testable solution --- addons/web/static/src/js/chrome/search_inputs.js | 14 +++++++++++--- addons/web/static/src/js/fields/field_utils.js | 10 +++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js index 29db17df252..6c88d82a5d5 100644 --- a/addons/web/static/src/js/chrome/search_inputs.js +++ b/addons/web/static/src/js/chrome/search_inputs.js @@ -6,6 +6,7 @@ var core = require('web.core'); var Domain = require('web.Domain'); var field_utils = require('web.field_utils'); var pyeval = require('web.pyeval'); +var session = require('web.session'); var time = require('web.time'); var utils = require('web.utils'); var Widget = require('web.Widget'); @@ -346,18 +347,25 @@ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ complete: function (needle) { // Make sure the needle has a correct format before the creation of the moment object. See // issue https://github.com/moment/moment/issues/1407 - var parseFormatOptions = {timezone: true}; var t, v; try { t = (this.attrs && this.attrs.type === 'datetime') ? 'datetime' : 'date'; - v = field_utils.parse[t](needle, {type: t}, parseFormatOptions); + v = field_utils.parse[t](needle, {type: t}, {timezone: true}); } catch (e) { return $.when(null); } var m = moment(v, t === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); + + if (t === 'date') { + // at this point, m is SOMEDAY at midnight BUT + // Constructed from a "v" object that was forced in utc + // So we need to apply timezone correction here + m = m.clone().add(-session.getTZOffset(m), 'minutes'); + } + if (!m.isValid()) { return $.when(null); } - var date_string = field_utils.format[t](m, {type: t}, parseFormatOptions); + var date_string = field_utils.format[t](m, {type: t}); var label = _.str.sprintf(_.str.escapeHTML( _t("Search %(field)s at: %(value)s")), { field: '' + _.escape(this.attrs.string) + '', diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index 7da03b023b8..5a11f1b0ba8 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -109,11 +109,10 @@ function formatDate(value, field, options) { if (value === false) { return ""; } - var fieldType = field && field.type; - if (fieldType === 'datetime' && - (!options || !('timezone' in options)) || - options && options.timezone) { + if (field && field.type === 'datetime') { + if (!options || !('timezone' in options) || options.timezone) { value = value.clone().add(session.getTZOffset(value), 'minutes'); + } } var date_format = time.getLangDateFormat(); return value.format(date_format); @@ -373,9 +372,6 @@ function parseDate(value, field, options) { date = moment.utc(value); } else { date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601], true); - if (options && options.timezone) { - date.add(-session.getTZOffset(date), 'minutes'); - } } if (date.isValid()) { if (date.year() === 0) { From 752545553495dc9cec73714989b8f5e610825bbc Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 13 Nov 2018 16:40:30 +0000 Subject: [PATCH 4/4] [WIP] looking good babe --- .../web/static/src/js/chrome/search_inputs.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js index 6c88d82a5d5..59126ecb37c 100644 --- a/addons/web/static/src/js/chrome/search_inputs.js +++ b/addons/web/static/src/js/chrome/search_inputs.js @@ -6,7 +6,6 @@ var core = require('web.core'); var Domain = require('web.Domain'); var field_utils = require('web.field_utils'); var pyeval = require('web.pyeval'); -var session = require('web.session'); var time = require('web.time'); var utils = require('web.utils'); var Widget = require('web.Widget'); @@ -342,7 +341,8 @@ var BooleanField = SelectionField.extend(/** @lends instance.web.search.BooleanF */ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ value_from: function (facetValue) { - return time.date_to_str(facetValue.get('value')); + var serverFormat = (this.attrs && this.attrs.type === 'datetime') ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; + return facetValue.get('valueAsMoment').clone().format(serverFormat); }, complete: function (needle) { // Make sure the needle has a correct format before the creation of the moment object. See @@ -357,13 +357,6 @@ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ var m = moment(v, t === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); - if (t === 'date') { - // at this point, m is SOMEDAY at midnight BUT - // Constructed from a "v" object that was forced in utc - // So we need to apply timezone correction here - m = m.clone().add(-session.getTZOffset(m), 'minutes'); - } - if (!m.isValid()) { return $.when(null); } var date_string = field_utils.format[t](m, {type: t}); var label = _.str.sprintf(_.str.escapeHTML( @@ -375,7 +368,7 @@ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ facet: { category: this.attrs.string, field: this, - values: [{label: date_string, value: m.toDate()}] + values: [{label: date_string, value: m.toDate(), valueAsMoment: m}] } }]); } @@ -392,11 +385,7 @@ var DateField = Field.extend(/** @lends instance.web.search.DateField# */{ * @class * @extends instance.web.DateField */ -var DateTimeField = DateField.extend(/** @lends instance.web.search.DateTimeField# */{ - value_from: function (facetValue) { - return time.datetime_to_str(facetValue.get('value')); - } -}); +var DateTimeField = DateField; var ManyToOneField = CharField.extend({ default_operator: {},