From fff7334d09efee0a7cf1d4410f90122a88cdc3d7 Mon Sep 17 00:00:00 2001 From: Bruno Boi Date: Mon, 22 Aug 2022 12:36:23 +0000 Subject: [PATCH] [FIX] web: use right datetime format in daterange field - First issue The format passed to the daterangepicker locale options could have been wrong as the daterangepicker lib uses moment and we would pass to it the date(time) format expressed with luxon's format tokens. Furthermore, there was no need to pass the format to the daterangepicker library as the DateRangeField component already handles this. - Second issue There was also another issue with the DateRangeField component: When applying the daterangepicker values, the conversion between a moment object to a luxon DateTime object was wrong because of the format used to convert the moment object to a string. - Explanation The format tokens are different between luxon and moment. - After this commit An helper function has been introduced in order to transform a luxon format into a moment format. The DateRangeField component now properly converts a moment object to a luxon's DateTime object when applying the daterangepicker selection. - Additional note Also, the DatePicker component was locally converting the luxon's datetime format and thus has been adapted to use the new helper. closes odoo/odoo#98578 Signed-off-by: Aaron Bohy (aab) --- .../static/src/core/datepicker/datepicker.js | 11 ++-------- addons/web/static/src/core/l10n/dates.js | 21 +++++++++++++++++++ .../views/fields/daterange/daterange_field.js | 10 ++++----- .../views/fields/daterange_field_tests.js | 21 +++++++++++++++++++ 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/addons/web/static/src/core/datepicker/datepicker.js b/addons/web/static/src/core/datepicker/datepicker.js index 8db30ea4929..c66fefd9363 100644 --- a/addons/web/static/src/core/datepicker/datepicker.js +++ b/addons/web/static/src/core/datepicker/datepicker.js @@ -4,6 +4,7 @@ import { areDateEquals, formatDate, formatDateTime, + luxonToMomentFormat, parseDate, parseDateTime, } from "@web/core/l10n/dates"; @@ -33,14 +34,6 @@ function luxonDateToMomentDate(date) { return window.moment(String(date)); } -/** - * @param {string} format - * @returns {string} - */ -function luxonFormatToMomentFormat(format) { - return format.replace(/[dy]/g, (x) => x.toUpperCase()); -} - /** * @param {string} format * @returns {boolean} @@ -198,7 +191,7 @@ export class DatePicker extends Component { const params = { ...commandOrParams, date: this.date || null, - format: luxonFormatToMomentFormat(format), + format: luxonToMomentFormat(format), locale: commandOrParams.locale || (this.date && this.date.locale), }; for (const prop in params) { diff --git a/addons/web/static/src/core/l10n/dates.js b/addons/web/static/src/core/l10n/dates.js index d2efb6dc443..78bd7175c15 100644 --- a/addons/web/static/src/core/l10n/dates.js +++ b/addons/web/static/src/core/l10n/dates.js @@ -44,6 +44,14 @@ const normalizeFormatTable = { X: "HH:mm:ss", }; +const luxonToMomentFormatTable = { + c: "d", + d: "D", + o: "DDDD", + a: "A", + y: "Y", +}; + const smartDateUnits = { d: "days", m: "months", @@ -143,6 +151,19 @@ export const strftimeToLuxonFormat = memoize(function strftimeToLuxonFormat(valu return output.join(""); }); +/** + * Converts a Luxon format to a moment.js format. + * NB: this is not a complete conversion, only the supported tokens are converted. + * + * @param {string} value original format + * @returns {string} valid moment.js format + */ +export const luxonToMomentFormat = memoize(function luxonToMomentFormat(format) { + return format.replace(alphaRegex, (match) => { + return luxonToMomentFormatTable[match] || match; + }); +}); + // ----------------------------------------------------------------------------- // Formatting // ----------------------------------------------------------------------------- diff --git a/addons/web/static/src/views/fields/daterange/daterange_field.js b/addons/web/static/src/views/fields/daterange/daterange_field.js index 5ffe6886baa..89e70cc74f1 100644 --- a/addons/web/static/src/views/fields/daterange/daterange_field.js +++ b/addons/web/static/src/views/fields/daterange/daterange_field.js @@ -3,6 +3,7 @@ import { localization } from "@web/core/l10n/localization"; import { registry } from "@web/core/registry"; import { loadJS } from "@web/core/assets"; +import { luxonToMomentFormat } from "@web/core/l10n/dates"; import { useService } from "@web/core/utils/hooks"; import { standardFieldProps } from "../standard_field_props"; @@ -16,6 +17,9 @@ export class DateRangeField extends Component { this.root = useRef("root"); this.isPickerShown = false; this.pickerContainer; + this.momentFormat = luxonToMomentFormat( + this.isDateTime ? localization.dateTimeFormat : localization.dateFormat + ); useExternalListener(window, "scroll", this.onWindowScroll, { capture: true }); onWillStart(() => loadJS("/web/static/lib/daterangepicker/daterangepicker.js")); @@ -32,9 +36,6 @@ export class DateRangeField extends Component { locale: { applyLabel: this.env._t("Apply"), cancelLabel: this.env._t("Cancel"), - format: this.isDateTime - ? localization.dateTimeFormat - : localization.dateFormat, }, startDate: start ? window.moment(start) : window.moment(), endDate: end ? window.moment(end) : window.moment(), @@ -126,10 +127,9 @@ export class DateRangeField extends Component { async onPickerApply(ev, picker) { const start = this.isDateTime ? picker.startDate : picker.startDate.startOf("day"); const end = this.isDateTime ? picker.endDate : picker.endDate.startOf("day"); - const format = this.isDateTime ? localization.dateTimeFormat : localization.dateFormat; const parser = parsers.get(this.props.formatType); const dates = [start, end].map((date) => { - return parser(date.format(format.toUpperCase()), { + return parser(date.format(this.momentFormat), { timezone: this.isDateTime, }); }); diff --git a/addons/web/static/tests/views/fields/daterange_field_tests.js b/addons/web/static/tests/views/fields/daterange_field_tests.js index 674f2a5f2f5..b3e83d7fa2e 100644 --- a/addons/web/static/tests/views/fields/daterange_field_tests.js +++ b/addons/web/static/tests/views/fields/daterange_field_tests.js @@ -191,6 +191,27 @@ QUnit.module("Fields", (hooks) => { "30", "active end date minute should be '30' in date range picker" ); + + // Select a new range and check that inputs are updated + await triggerEvent(datepicker, ".start-date", "mousedown"); // 02/08/2017 + await triggerEvent(datepicker, ".start-date + .available", "mousedown"); // 02/09/2017 + await click(datepicker, ".applyBtn"); + assert.equal( + target.querySelector("[name=datetime] input").value, + "02/08/2017 15:30:00" + ); + assert.equal( + target.querySelector("[name=datetime_end] input").value, + "02/09/2017 05:30:00" + ); + + // Save + await click(target, ".o_form_button_save"); + fields = target.querySelectorAll(".o_field_daterange"); + + // Check date after save + assert.strictEqual(fields[0].textContent, "02/08/2017 15:30:00"); + assert.strictEqual(fields[fields.length - 1].textContent, "02/09/2017 05:30:00"); } );