[FIX] web: Force proper numbering system for server dates
Before this commit, the numbering system used by luxon to parse and format dates was the one set by the current locale. This means that any date sent to and received from the server would also be expected to use the current locale and numbering system. The consequence of this behavior is that any domain generated by arbitrary dates would trigger a server error. This commit forces the numbering system to 'latn' (browser default) for dates used by the server. closes odoo/odoo#85487 X-original-commit: c8ef1fcb2277993f90a8e237dbd415de7d1b88bc Related: odoo/enterprise#24822 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com> Signed-off-by: Julien Mougenot (jum) <jum@odoo.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { _t } from "@web/core/l10n/translation";
|
||||
import { memoize } from "@web/core/utils/functions";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
const { DateTime, Settings } = luxon;
|
||||
|
||||
const SERVER_DATE_FORMAT = "yyyy-MM-dd";
|
||||
const SERVER_TIME_FORMAT = "HH:mm:ss";
|
||||
@@ -174,6 +174,12 @@ export function formatDate(value, options = {}) {
|
||||
*
|
||||
* Default=false.
|
||||
*
|
||||
* @param {string} [options.numberingSystem]
|
||||
* Provided numbering system used to parse the input value.
|
||||
*
|
||||
* Default=the default numbering system assigned to luxon
|
||||
* @see localization_service.js
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatDateTime(value, options = {}) {
|
||||
@@ -181,9 +187,10 @@ export function formatDateTime(value, options = {}) {
|
||||
return "";
|
||||
}
|
||||
const format = options.format || localization.dateTimeFormat;
|
||||
const numberingSystem = options.numberingSystem || Settings.defaultNumberingSystem;
|
||||
const zone = options.timezone ? "system" : "utc";
|
||||
value = value.setZone(zone, { keepLocaltime: options.timezone });
|
||||
return value.toFormat(format);
|
||||
return value.toFormat(format, { numberingSystem });
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -231,6 +238,17 @@ export function parseDate(value, options = {}) {
|
||||
*
|
||||
* Default=false.
|
||||
*
|
||||
* @param {string} [options.locale]
|
||||
* Provided locale used to parse the input value.
|
||||
*
|
||||
* Default=the session localization locale
|
||||
*
|
||||
* @param {string} [options.numberingSystem]
|
||||
* Provided numbering system used to parse the input value.
|
||||
*
|
||||
* Default=the default numbering system assigned to luxon
|
||||
* @see localization_service.js
|
||||
*
|
||||
* @returns {DateTime | false} Luxon DateTime object
|
||||
*/
|
||||
export function parseDateTime(value, options = {}) {
|
||||
@@ -243,6 +261,7 @@ export function parseDateTime(value, options = {}) {
|
||||
setZone: true,
|
||||
zone: options.timezone ? "system" : "utc",
|
||||
locale: options.locale,
|
||||
numberingSystem: options.numberingSystem || Settings.defaultNumberingSystem,
|
||||
};
|
||||
|
||||
let result = constrain(parseSmartDateInput(value));
|
||||
@@ -283,13 +302,40 @@ export function parseDateTime(value, options = {}) {
|
||||
return options.timezone ? result : result.toUTC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date object parsed from the given serialized string.
|
||||
* @param {string} value
|
||||
* @returns {DateTime | false}
|
||||
*/
|
||||
export const deserializeDate = (value) => {
|
||||
return parseDate(value, {
|
||||
format: SERVER_DATE_FORMAT,
|
||||
numberingSystem: "latn",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a datetime object parsed from the given serialized string.
|
||||
* @param {string} value
|
||||
* @returns {DateTime | false}
|
||||
*/
|
||||
export const deserializeDateTime = (value) => {
|
||||
return parseDateTime(value, {
|
||||
format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`,
|
||||
numberingSystem: "latn",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a serialized string representing the given date.
|
||||
* @param {DateTime} value
|
||||
* @returns {string}
|
||||
*/
|
||||
export const serializeDate = (value) => {
|
||||
return formatDate(value, { format: SERVER_DATE_FORMAT });
|
||||
return formatDate(value, {
|
||||
format: SERVER_DATE_FORMAT,
|
||||
numberingSystem: "latn",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -298,5 +344,8 @@ export const serializeDate = (value) => {
|
||||
* @returns {string}
|
||||
*/
|
||||
export const serializeDateTime = (value) => {
|
||||
return formatDateTime(value, { format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}` });
|
||||
return formatDateTime(value, {
|
||||
format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`,
|
||||
numberingSystem: "latn",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { serializeDate, serializeDateTime } from "@web/core/l10n/dates";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
|
||||
export const DEFAULT_PERIOD = "this_month";
|
||||
@@ -209,18 +210,21 @@ export function constructDateRange(params) {
|
||||
setParam.month = QUARTERS[setParam.quarter].coveredMonths[0];
|
||||
delete setParam.quarter;
|
||||
}
|
||||
const date = referenceMoment.set(setParam).plus(plusParam || {});
|
||||
const date = referenceMoment
|
||||
.set(setParam)
|
||||
.plus(plusParam || {})
|
||||
.setZone("utc", { keepLocalTime: true });
|
||||
// compute domain
|
||||
let leftDate = date.startOf(granularity);
|
||||
let rightDate = date.endOf(granularity);
|
||||
let leftBound;
|
||||
let rightBound;
|
||||
if (fieldType === "date") {
|
||||
leftBound = leftDate.toFormat("yyyy-MM-dd");
|
||||
rightBound = rightDate.toFormat("yyyy-MM-dd");
|
||||
leftBound = serializeDate(leftDate);
|
||||
rightBound = serializeDate(rightDate);
|
||||
} else {
|
||||
leftBound = leftDate.toUTC().toFormat("yyyy-MM-dd HH:mm:ss");
|
||||
rightBound = rightDate.toUTC().toFormat("yyyy-MM-dd HH:mm:ss");
|
||||
leftBound = serializeDateTime(leftDate);
|
||||
rightBound = serializeDateTime(rightDate);
|
||||
}
|
||||
const domain = new Domain(["&", [fieldName, ">=", leftBound], [fieldName, "<=", rightBound]]);
|
||||
// compute description
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
parseDate,
|
||||
parseDateTime,
|
||||
strftimeToLuxonFormat,
|
||||
serializeDate,
|
||||
serializeDateTime,
|
||||
deserializeDate,
|
||||
deserializeDateTime,
|
||||
} from "@web/core/l10n/dates";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { patch, unpatch } from "@web/core/utils/patch";
|
||||
@@ -359,6 +363,80 @@ QUnit.module(
|
||||
unpatch(localization, "default loc");
|
||||
});
|
||||
|
||||
QUnit.test("serializeDate", async (assert) => {
|
||||
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
|
||||
assert.strictEqual(date.toFormat("yyyy-MM-dd"), "2022-02-21");
|
||||
assert.strictEqual(serializeDate(date), "2022-02-21");
|
||||
});
|
||||
|
||||
QUnit.test("serializeDate with different numbering system", async (assert) => {
|
||||
patchWithCleanup(Settings, { defaultNumberingSystem: "arab" });
|
||||
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
|
||||
assert.strictEqual(date.toFormat("yyyy-MM-dd"), "٢٠٢٢-٠٢-٢١");
|
||||
assert.strictEqual(serializeDate(date), "2022-02-21");
|
||||
});
|
||||
|
||||
QUnit.test("serializeDateTime", async (assert) => {
|
||||
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
|
||||
assert.strictEqual(date.toFormat("yyyy-MM-dd HH:mm:ss"), "2022-02-21 16:11:42");
|
||||
assert.strictEqual(serializeDateTime(date), "2022-02-21 16:11:42");
|
||||
});
|
||||
|
||||
QUnit.test("serializeDateTime with different numbering system", async (assert) => {
|
||||
patchWithCleanup(Settings, { defaultNumberingSystem: "arab" });
|
||||
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
|
||||
assert.strictEqual(date.toFormat("yyyy-MM-dd HH:mm:ss"), "٢٠٢٢-٠٢-٢١ ١٦:١١:٤٢");
|
||||
assert.strictEqual(serializeDateTime(date), "2022-02-21 16:11:42");
|
||||
});
|
||||
|
||||
QUnit.test("deserializeDate", async (assert) => {
|
||||
const date = DateTime.utc(2022, 2, 21);
|
||||
assert.strictEqual(
|
||||
DateTime.fromFormat("2022-02-21", "yyyy-MM-dd", { zone: "utc" }).toMillis(),
|
||||
date.toMillis()
|
||||
);
|
||||
assert.strictEqual(deserializeDate("2022-02-21").toMillis(), date.toMillis());
|
||||
});
|
||||
|
||||
QUnit.test("deserializeDate with different numbering system", async (assert) => {
|
||||
patchWithCleanup(Settings, { defaultNumberingSystem: "arab" });
|
||||
const date = DateTime.utc(2022, 2, 21);
|
||||
assert.strictEqual(
|
||||
DateTime.fromFormat("٢٠٢٢-٠٢-٢١", "yyyy-MM-dd", { zone: "utc" }).toMillis(),
|
||||
date.toMillis()
|
||||
);
|
||||
assert.strictEqual(deserializeDate("2022-02-21").toMillis(), date.toMillis());
|
||||
});
|
||||
|
||||
QUnit.test("deserializeDateTime", async (assert) => {
|
||||
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
|
||||
assert.strictEqual(
|
||||
DateTime.fromFormat("2022-02-21 16:11:42", "yyyy-MM-dd HH:mm:ss", {
|
||||
zone: "utc",
|
||||
}).toMillis(),
|
||||
date.toMillis()
|
||||
);
|
||||
assert.strictEqual(
|
||||
deserializeDateTime("2022-02-21 16:11:42").toMillis(),
|
||||
date.toMillis()
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("deserializeDateTime with different numbering system", async (assert) => {
|
||||
patchWithCleanup(Settings, { defaultNumberingSystem: "arab" });
|
||||
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
|
||||
assert.strictEqual(
|
||||
DateTime.fromFormat("٢٠٢٢-٠٢-٢١ ١٦:١١:٤٢", "yyyy-MM-dd HH:mm:ss", {
|
||||
zone: "utc",
|
||||
}).toMillis(),
|
||||
date.toMillis()
|
||||
);
|
||||
assert.strictEqual(
|
||||
deserializeDateTime("2022-02-21 16:11:42").toMillis(),
|
||||
date.toMillis()
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.module("dates utils compatibility with legacy", {
|
||||
beforeEach() {
|
||||
patchWithCleanup(localization, {
|
||||
|
||||
Reference in New Issue
Block a user