summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorChristophe Matthieu <chm@odoo.com>2017-04-12 11:53:42 +0200
committerChristophe Matthieu <chm@odoo.com>2017-04-13 16:55:00 +0200
commitc32724eae06c987e74a0600f1669696e499edc33 (patch)
treee80d8ccc3c08ec4a95889a06f6624e2c63b5bb29 /addons/web/static/src
parent9c43ad85d463ef8f1fb4b8d16924bc71c0885a55 (diff)
[FIX] web: calendar timezone with user configuration
The client must receive the tzOffset to apply this on all hours. To use the date picker we must change the date to apply the change in the user's tzOffset, without this change the result is wrong. For datetime widget we must change like it to avoid max stack error. The server send the tzOffset, if it's undefined, by default the client use the browser time offset. Every test are change to use tzOffset. A test is added in calendar to use the click by position for the fullcalendar lib. This test create and drag and drop an event to check timezone error and error when we use default value in the context. Use formating date like 2026-04-04T08:00:00Z instead of 2026-04-04 08:00:00 is important for phantomjs, because it's crash without information if the formating is not the standard format. For the parsing, when the client receive a server date the date is utc formating but when it's parsed from the client is in the user's timezone
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/js/core/session.js4
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js49
-rw-r--r--addons/web/static/src/js/fields/field_utils.js45
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js12
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js45
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js6
-rw-r--r--addons/web/static/src/less/web_calendar.less4
7 files changed, 116 insertions, 49 deletions
diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js
index 0bd9dc19ad4..3a098bbe78c 100644
--- a/addons/web/static/src/js/core/session.js
+++ b/addons/web/static/src/js/core/session.js
@@ -285,6 +285,10 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
var result = _.extend({}, window.odoo.session_info);
delete result.session_id;
_.extend(this, result);
+ if (!this.tzOffset) {
+ // send by server to have the user timezone and not the browser timezone
+ this.tzOffset = -new Date().getTimezoneOffset();
+ }
return $.when();
},
check_session_id: function () {
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index af5d9bcf233..69fd7c5b791 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -267,8 +267,9 @@ var FieldDate = InputField.extend({
if (this.mode === 'edit') {
this.datewidget = this._makeDatePicker();
this.datewidget.on('datetime_changed', this, function () {
- if (!this.datewidget.get_value().isSame(this.value)) {
- this._setValue(this.datewidget.get_value());
+ var value = this._getDateWidgetValue();
+ if (!value.isSame(this.value)) {
+ this._setValue(value);
}
});
def = this.datewidget.appendTo('<div>').done(function () {
@@ -286,6 +287,15 @@ var FieldDate = InputField.extend({
//--------------------------------------------------------------------------
/**
+ * return the datepicker value
+ *
+ * @private
+ */
+ _getDateWidgetValue: function () {
+ return this.datewidget.get_value();
+ },
+
+ /**
* Instantiates a new DateWidget datepicker.
*
* @private
@@ -315,13 +325,46 @@ var FieldDateTime = FieldDate.extend({
//--------------------------------------------------------------------------
/**
+ * return the datepicker value
+ *
+ * @private
+ */
+ _getDateWidgetValue: function () {
+ return this.datewidget.get_value().add(-this.getSession().tzOffset, 'minutes');
+ },
+
+ /**
* Instantiates a new DateTimeWidget datepicker rather than DateWidget.
*
* @override
* @private
*/
_makeDatePicker: function () {
- return new datepicker.DateTimeWidget(this, {defaultDate: this.value});
+ var value = this.value && this.value.clone().add(this.getSession().tzOffset, 'minutes');
+ return new datepicker.DateTimeWidget(this, {defaultDate: value});
+ },
+
+ /**
+ * Set the datepicker to the right value rather than the default one.
+ *
+ * @override
+ * @private
+ */
+ _renderEdit: function () {
+ var value = this.value && this.value.clone().add(this.getSession().tzOffset, 'minutes');
+ this.datewidget.set_value(value);
+ this.$input = this.datewidget.$input;
+ },
+
+ /**
+ * FieldDateTime overrides _formatValue to use the session timezone
+ *
+ * @override
+ * @private
+ * @param {Moment} value
+ */
+ _formatValue: function (value) {
+ return field_utils.format.datetime(value, {timezone: true});
},
});
diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js
index a086ebefd15..d6677291d47 100644
--- a/addons/web/static/src/js/fields/field_utils.js
+++ b/addons/web/static/src/js/fields/field_utils.js
@@ -79,10 +79,13 @@ function formatDate(value) {
* @params {Moment|false}
* @returns {string}
*/
-function formatDateTime(value) {
+function formatDateTime(value, options) {
if (!value) {
return "";
}
+ if (!options || !('timezone' in options) || options.timezone) {
+ value = value.clone().add(session.tzOffset, 'minutes');
+ }
var l10n = core._t.database.parameters;
var date_format = time.strftime_to_moment_format(l10n.date_format);
var time_format = time.strftime_to_moment_format(l10n.time_format);
@@ -267,24 +270,26 @@ function formatSelection(value, field) {
* @params {string}
* @returns {Moment|false} Moment date object
*/
-function parseDate(value) {
+function parseDate(value, field, options) {
if (!value) {
return false;
}
var date_pattern = time.strftime_to_moment_format(core._t.database.parameters.date_format);
var date_pattern_wo_zero = date_pattern.replace('MM','M').replace('DD','D');
- var date = moment.utc(value, [date_pattern, date_pattern_wo_zero, moment.ISO_8601], true);
- if (date.isValid() && date.year() >= 1900) {
- date.toJSON = function () {return this.utc().format('YYYY-MM-DD');};
- return date;
+ var date;
+ if (options && options.isUTC) {
+ date = moment.utc(value);
+ } else {
+ date = moment.utc(value, [date_pattern, date_pattern_wo_zero, moment.ISO_8601], true);
}
- date = moment.utc(value, [date_pattern, date_pattern_wo_zero, moment.ISO_8601]);
- if (date.isValid()) {
+ if (date.isValid() && date.year() >= 1900) {
if (date.year() === 0) {
date.year(moment.utc().year());
}
if (date.year() >= 1900) {
- date.toJSON = function () {return this.utc().format('YYYY-MM-DD');};
+ date.toJSON = function () {
+ return this.format('YYYY-MM-DD');
+ };
return date;
}
}
@@ -298,7 +303,7 @@ function parseDate(value) {
* @params {string}
* @returns {Moment|false} Moment date object
*/
-function parseDateTime(value) {
+function parseDateTime(value, field, options) {
if (!value) {
return false;
}
@@ -308,20 +313,24 @@ function parseDateTime(value) {
time_pattern_wo_zero = time_pattern.replace('HH','H').replace('mm','m').replace('ss','s');
var pattern1 = date_pattern + ' ' + time_pattern;
var pattern2 = date_pattern_wo_zero + ' ' + time_pattern_wo_zero;
- var datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601], true);
- if (datetime.isValid() && datetime.year() >= 1900) {
- datetime.toJSON = function () {
- return this.utc().format('YYYY-MM-DD HH:mm:ss');
- };
- return datetime;
+ var datetime;
+ if (options && options.isUTC) {
+ // phatomjs crash if we don't use this format
+ datetime = moment.utc(value.replace(' ', 'T') + 'Z');
+ } else {
+ datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601], true);
+ if (options && options.timezone) {
+ datetime.add(-session.tzOffset, 'minutes');
+ }
}
- datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601]);
if (datetime.isValid()) {
if (datetime.year() === 0) {
datetime.year(moment.utc().year());
}
if (datetime.year() >= 1900) {
- datetime.toJSON = function () {return this.utc().format('YYYY-MM-DD HH:mm:ss');};
+ datetime.toJSON = function () {
+ return this.format('YYYY-MM-DD HH:mm:ss');
+ };
return datetime;
}
}
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 4de6ca25187..71d6d6fa1ad 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1023,10 +1023,10 @@ var BasicModel = AbstractModel.extend({
});
} else if (field.type === 'date') {
// process data: convert into a moment instance
- record._changes[name] = fieldUtils.parse.date(val);
+ record._changes[name] = fieldUtils.parse.date(val, field, {isUTC: true});
} else if (field.type === 'datetime') {
// process datetime: convert into a moment instance
- record._changes[name] = fieldUtils.parse.datetime(val);
+ record._changes[name] = fieldUtils.parse.datetime(val, field, {isUTC: true});
} else {
record._changes[name] = val;
}
@@ -2195,10 +2195,10 @@ var BasicModel = AbstractModel.extend({
});
} else if (field.type === 'date') {
// process date: convert into a moment instance
- record._changes[name] = fieldUtils.parse.date(result[name]);
+ record._changes[name] = fieldUtils.parse.date(result[name], field, {isUTC: true});
} else if (field.type === 'datetime') {
// process datetime: convert into a moment instance
- record._changes[name] = fieldUtils.parse.datetime(result[name]);
+ record._changes[name] = fieldUtils.parse.datetime(result[name], field, {isUTC: true});
} else {
record._changes[name] = result[name];
}
@@ -2268,10 +2268,10 @@ var BasicModel = AbstractModel.extend({
}
} else if (field.type === 'date') {
// process data: convert into a moment instance
- record[fieldName] = fieldUtils.parse.date(val);
+ record[fieldName] = fieldUtils.parse.date(val, field, {isUTC: true});
} else if (field.type === 'datetime') {
// process datetime: convert into a moment instance
- record[fieldName] = fieldUtils.parse.datetime(val);
+ record[fieldName] = fieldUtils.parse.datetime(val, field, {isUTC: true});
}
});
},
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 e7320aed5ee..c2f405e1b40 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -3,7 +3,7 @@ odoo.define('web.CalendarModel', function (require) {
var AbstractModel = require('web.AbstractModel');
var core = require('web.core');
-var field_utils = require('web.field_utils');
+var fieldUtils = require('web.field_utils');
var session = require('web.session');
var time = require('web.time');
@@ -35,16 +35,16 @@ return AbstractModel.extend({
calendarEventToRecord: function (event) {
// Normalize event_end without changing fullcalendars event.
var data = {'name': event.title};
- var start = event.start;
- var end = event.end;
+ var start = event.start.clone();
+ var end = event.end && event.end.clone();
- //Bug when we move an all_day event from week or day view, we don't have
- // a date.end or duration...
- if (end === null || end.diff(start) < 0) {
+ if (!end || end.diff(start) < 0) {
end = start.clone().add(1, 'h');
}
- if (event.allDay) {
+ if (event.allDay || end.diff(start) === 86400000) {
+ event.allDay = true;
+
if (this.scale === 'month') {
if (event.r_start) {
start.hours(event.r_start.hours())
@@ -61,12 +61,16 @@ return AbstractModel.extend({
end.utc();
}
} else if (this.mapping.all_day) {
- start.utc().startOf('day');
- end.utc().startOf('day');
+ start.startOf('day');
+ end.startOf('day');
} else {
- start.utc().hours(7);
- end.utc().hours(19);
+ // default hours in the user's timezone
+ start.hours(7).add(-this.getSession().tzOffset, 'minutes');
+ end.hours(19).add(-this.getSession().tzOffset, 'minutes');
}
+ } else {
+ start.add(-this.getSession().tzOffset, 'minutes');
+ end.add(-this.getSession().tzOffset, 'minutes');
}
if (this.mapping.all_day) {
@@ -440,7 +444,7 @@ return AbstractModel.extend({
var _value = record[filter.write_field];
var value = _.isArray(_value) ? _value[0] : _value;
var f = _.find(filter.filters, function (f) {return f.value === value;});
- var formater = field_utils.format[_.contains(['many2many', 'one2many'], field.type) ? 'many2one' : field.type];
+ var formater = fieldUtils.format[_.contains(['many2many', 'one2many'], field.type) ? 'many2one' : field.type];
return {
'id': record.id,
'value': value,
@@ -520,7 +524,7 @@ return AbstractModel.extend({
fs.push({
'color_index': self.model_color === (field.relation || element.model) ? value : false,
'value': value,
- 'label': field_utils.format[field.type](_value, field),
+ 'label': fieldUtils.format[field.type](_value, field),
'avatar_model': field.relation || element.model,
});
});
@@ -572,21 +576,24 @@ return AbstractModel.extend({
attendees = [];
if (!all_day) {
- date_start = field_utils.parse.datetime(evt[this.mapping.date_start]);
- date_stop = this.mapping.date_stop ? field_utils.parse.datetime(evt[this.mapping.date_stop]) : null;
+ date_start = fieldUtils.parse.datetime(evt[this.mapping.date_start]);
+ date_stop = this.mapping.date_stop ? fieldUtils.parse.datetime(evt[this.mapping.date_stop]) : null;
} else {
- date_start = field_utils.parse.datetime(evt[this.mapping.date_start].split(' ')[0],'start');
- date_stop = this.mapping.date_stop ? field_utils.parse.datetime(evt[this.mapping.date_stop].split(' ')[0],'start') : null;
+ date_start = fieldUtils.parse.datetime(evt[this.mapping.date_start].split(' ')[0]);
+ date_stop = this.mapping.date_stop ? fieldUtils.parse.datetime(evt[this.mapping.date_stop].split(' ')[0]) : null;
}
if (!date_stop && date_delay) {
date_stop = date_start.clone().add(date_delay,'hours');
}
+ date_start.add(this.getSession().tzOffset, 'minutes');
+ date_stop.add(this.getSession().tzOffset, 'minutes');
+
var r = {
'record': evt,
- 'start': dateToServer(date_start),
- 'end': dateToServer(date_stop),
+ 'start': date_start,
+ 'end': date_stop,
'r_start':date_start.clone(),
'r_end':date_stop.clone(),
'title': the_title,
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 3e4a0f4382f..4abab997666 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -60,7 +60,7 @@ var DateWidget = Widget.extend({
}
},
get_value: function() {
- return this.get('value');
+ return this.get('value').clone();
},
set_value_from_ui: function() {
var value = this.$input.val() || false;
@@ -84,10 +84,10 @@ var DateWidget = Widget.extend({
}
},
parse_client: function(v) {
- return field_utils.parse[this.type_of_date](v, {type: this.type_of_date});
+ return field_utils.parse[this.type_of_date](v, {timezone: false});
},
format_client: function(v) {
- return field_utils.format[this.type_of_date](v, {type: this.type_of_date});
+ return field_utils.format[this.type_of_date](v, {timezone: false});
},
set_datetime_default: function() {
//when opening datetimepicker the date and time by default should be the one from
diff --git a/addons/web/static/src/less/web_calendar.less b/addons/web/static/src/less/web_calendar.less
index b51db0d4150..740d6db14d9 100644
--- a/addons/web/static/src/less/web_calendar.less
+++ b/addons/web/static/src/less/web_calendar.less
@@ -196,6 +196,10 @@
}
}
}
+
+ .fc-time-grid-event.fc-short .fc-time:before {
+ content: '';
+ }
}
.modal {