[IMP] web: account for local timezone when formatting dates

formatDate and formatDateTime had their default 'timezone' option set to
false but this was changed to true in order to have the same behavior as
the legacy formatDate and formatDateTime functions.

Part-of: odoo/odoo#96946
This commit is contained in:
Bruno Boi
2022-08-02 09:19:56 +02:00
parent 400ffa0861
commit 9d1786c361
8 changed files with 121 additions and 77 deletions
@@ -44,8 +44,10 @@ registerModel({
return format.date(moment.utc(this.value));
}
return format.date(this.value);
case 'datetime':
return formatters.get("datetime")(deserializeDateTime(this.value), { timezone: true });
case 'datetime': {
const value = this.value ? deserializeDateTime(this.value) : this.value;
return formatters.get("datetime")(value, { timezone: true });
}
case 'float':
return format.float(this.value);
case 'integer':
+9 -20
View File
@@ -9,6 +9,7 @@ const { DateTime, Settings } = luxon;
const SERVER_DATE_FORMAT = "yyyy-MM-dd";
const SERVER_TIME_FORMAT = "HH:mm:ss";
const SERVER_DATETIME_FORMAT = `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`;
// -----------------------------------------------------------------------------
// Helpers
@@ -173,11 +174,11 @@ export function formatDate(value, options = {}) {
*
* Default=the session localization format.
*
* @param {boolean} [options.timezone]
* @param {boolean} [options.timezone=true]
* - True = input will be set in local time before being formatted.
* - False = input will be set in UTC time before being formatted.
*
* Default=false.
* Default=true.
*
* @param {string} [options.numberingSystem]
* Provided numbering system used to parse the input value.
@@ -193,7 +194,7 @@ export function formatDateTime(value, options = {}) {
}
const format = options.format || localization.dateTimeFormat;
const numberingSystem = options.numberingSystem || Settings.defaultNumberingSystem || "latn";
const zone = options.timezone ? "default" : "utc";
const zone = !("timezone" in options) || options.timezone ? "default" : "utc";
value = value.setZone(zone, { keepLocaltime: options.timezone });
return value.toFormat(format, { numberingSystem });
}
@@ -236,7 +237,7 @@ export function parseDate(value, options = {}) {
*
* Default=the session localization format
*
* @param {boolean} [options.timezone]
* @param {boolean} [options.timezone=false]
* - True = input value is considered being in localtime.
* - False = input value is considered being in utc time, and the returned
* value will have the UTC zone.
@@ -345,10 +346,7 @@ export function parseDateTime(value, options = {}) {
* @returns {DateTime | false}
*/
export function deserializeDate(value) {
return parseDate(value, {
format: SERVER_DATE_FORMAT,
numberingSystem: "latn",
});
return DateTime.fromSQL(value, { zone: "utc", numberingSystem: "latn" });
}
/**
@@ -357,10 +355,7 @@ export function deserializeDate(value) {
* @returns {DateTime | false}
*/
export function deserializeDateTime(value) {
return parseDateTime(value, {
format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`,
numberingSystem: "latn",
});
return DateTime.fromSQL(value, { zone: "utc", numberingSystem: "latn" });
}
/**
@@ -369,10 +364,7 @@ export function deserializeDateTime(value) {
* @returns {string}
*/
export function serializeDate(value) {
return formatDate(value, {
format: SERVER_DATE_FORMAT,
numberingSystem: "latn",
});
return value.setZone("utc").toFormat(SERVER_DATE_FORMAT, { numberingSystem: "latn" });
}
/**
@@ -381,8 +373,5 @@ export function serializeDate(value) {
* @returns {string}
*/
export function serializeDateTime(value) {
return formatDateTime(value, {
format: `${SERVER_DATE_FORMAT} ${SERVER_TIME_FORMAT}`,
numberingSystem: "latn",
});
return value.setZone("utc").toFormat(SERVER_DATETIME_FORMAT, { numberingSystem: "latn" });
}
@@ -60,12 +60,8 @@ class GetMetadataDialog extends Component {
this.state.creator = formatMany2one(metadata.create_uid);
this.state.lastModifiedBy = formatMany2one(metadata.write_uid);
this.state.noupdate = metadata.noupdate;
this.state.createDate = formatDateTime(parseDateTime(metadata.create_date), {
timezone: true,
});
this.state.writeDate = formatDateTime(parseDateTime(metadata.write_date), {
timezone: true,
});
this.state.createDate = formatDateTime(parseDateTime(metadata.create_date));
this.state.writeDate = formatDateTime(parseDateTime(metadata.write_date));
}
}
GetMetadataDialog.template = "web.DebugMenu.GetMetadataDialog";
@@ -479,7 +479,7 @@ export class Record extends DataPoint {
if (data[fieldName]) {
const deserialize =
fieldType === "date" ? deserializeDate : deserializeDateTime;
data[fieldName] = deserialize(JSON.stringify(data[fieldName]));
data[fieldName] = deserialize(data[fieldName].toJSON());
}
break;
}
+2 -6
View File
@@ -158,12 +158,8 @@ class GetMetadataDialog extends Component {
this.state.noupdate = metadata.noupdate;
this.state.creator = formatMany2one(metadata.create_uid);
this.state.lastModifiedBy = formatMany2one(metadata.write_uid);
this.state.createDate = formatDateTime(parseDateTime(metadata.create_date), {
timezone: true,
});
this.state.writeDate = formatDateTime(parseDateTime(metadata.write_date), {
timezone: true,
});
this.state.createDate = formatDateTime(parseDateTime(metadata.create_date));
this.state.writeDate = formatDateTime(parseDateTime(metadata.write_date));
}
}
GetMetadataDialog.template = "web.DebugMenu.GetMetadataDialog";
@@ -10,7 +10,7 @@ const { Component } = owl;
export class DateTimeField extends Component {
get formattedValue() {
return formatDateTime(this.props.value, { timezone: true });
return formatDateTime(this.props.value);
}
onDateTimeChanged(date) {
@@ -2468,15 +2468,9 @@ export class Group extends DataPoint {
return false;
}
if (type === "date") {
return serializeDate(
DateTime.fromFormat(range.to, "yyyy-MM-dd", { zone: "utc" }).minus({
day: 1,
})
);
return serializeDate(deserializeDate(range.to).minus({ day: 1 }));
} else {
return serializeDateTime(
DateTime.fromFormat(range.to, "yyyy-MM-dd HH:mm:ss").minus({ second: 1 })
);
return serializeDateTime(deserializeDateTime(range.to).minus({ second: 1 }));
}
}
default: {
+100 -33
View File
@@ -19,7 +19,7 @@ import session from "web.session";
import test_utils from "web.test_utils";
import { registerCleanup } from "../../helpers/cleanup";
import { defaultLocalization } from "../../helpers/mock_services";
import { patchWithCleanup } from "../../helpers/utils";
import { patchDate, patchWithCleanup } from "../../helpers/utils";
const { DateTime, Settings } = luxon;
@@ -119,42 +119,51 @@ QUnit.module(
unpatch(localization, "dateformat");
});
QUnit.test("formatDate (with DateTime.fromISO)", async (assert) => {
patchWithCleanup(localization, { dateFormat: "MM/dd/yyyy" });
// NB: the local timezone in test env is UTC+1
let date = DateTime.fromISO("2022-07-21T22:00:00Z");
assert.strictEqual(formatDate(date), "07/21/2022");
assert.strictEqual(formatDate(date, { timezone: true }), "07/21/2022");
date = DateTime.fromISO("2022-07-21T23:00:00Z");
assert.strictEqual(formatDate(date), "07/21/2022");
assert.strictEqual(formatDate(date, { timezone: true }), "07/22/2022");
date = DateTime.fromISO("2022-07-22T00:00:00Z");
assert.strictEqual(formatDate(date), "07/22/2022");
assert.strictEqual(formatDate(date, { timezone: true }), "07/22/2022");
// Here are the 3 same instants as above, but expressed from a different ISO string
date = DateTime.fromISO("2022-07-22T00:00:00+02:00");
assert.strictEqual(formatDate(date), "07/21/2022");
assert.strictEqual(formatDate(date, { timezone: true }), "07/21/2022");
date = DateTime.fromISO("2022-07-22T01:00:00+02:00");
assert.strictEqual(formatDate(date), "07/21/2022");
assert.strictEqual(formatDate(date, { timezone: true }), "07/22/2022");
date = DateTime.fromISO("2022-07-22T02:00:00+02:00");
assert.strictEqual(formatDate(date), "07/22/2022");
assert.strictEqual(formatDate(date, { timezone: true }), "07/22/2022");
});
QUnit.test("formatDateTime", async (assert) => {
patch(localization, "datetimeformat", { dateTimeFormat: "MM/dd/yyyy HH:mm:ss" });
let formatted = formatDateTime(DateTime.utc(2009, 5, 4, 12, 34, 23));
let expected = "05/04/2009 12:34:23";
assert.strictEqual(formatted, expected);
formatted = formatDateTime(DateTime.utc(2009, 5, 4, 12, 34, 23), { timezone: false });
assert.strictEqual(formatted, expected);
formatted = formatDateTime(DateTime.utc(2009, 5, 4, 12, 34, 23), { timezone: true });
expected = "05/04/2009 13:34:23";
assert.strictEqual(formatted, expected);
unpatch(localization, "datetimeformat");
patchWithCleanup(localization, { dateTimeFormat: "MM/dd/yyyy HH:mm:ss" });
const date = DateTime.utc(2009, 5, 4, 12, 34, 23);
assert.strictEqual(formatDateTime(date), "05/04/2009 13:34:23");
assert.strictEqual(formatDateTime(date, { timezone: false }), "05/04/2009 12:34:23");
});
QUnit.test("formatDateTime (with different timezone offset)", async (assert) => {
patch(localization, "datetimeformat", { dateTimeFormat: "MM/dd/yyyy HH:mm:ss" });
let str = formatDateTime(DateTime.utc(2017, 1, 1, 10, 0, 0, 0));
assert.strictEqual(str, "01/01/2017 10:00:00");
str = formatDateTime(DateTime.utc(2017, 6, 1, 10, 0, 0, 0));
assert.strictEqual(str, "06/01/2017 10:00:00");
str = formatDateTime(DateTime.utc(2017, 1, 1, 10, 0, 0, 0), { timezone: false });
assert.strictEqual(str, "01/01/2017 10:00:00");
str = formatDateTime(DateTime.utc(2017, 6, 1, 10, 0, 0, 0), { timezone: false });
assert.strictEqual(str, "06/01/2017 10:00:00");
str = formatDateTime(DateTime.utc(2017, 1, 1, 10, 0, 0, 0), { timezone: true });
assert.strictEqual(str, "01/01/2017 11:00:00");
str = formatDateTime(DateTime.utc(2017, 6, 1, 10, 0, 0, 0), { timezone: true });
assert.strictEqual(str, "06/01/2017 11:00:00");
unpatch(localization, "datetimeformat");
patchWithCleanup(localization, { dateTimeFormat: "MM/dd/yyyy HH:mm:ss" });
let date = DateTime.fromISO("2017-01-01T11:00:00+01:00");
assert.strictEqual(formatDateTime(date), "01/01/2017 11:00:00");
assert.strictEqual(formatDateTime(date, { timezone: false }), "01/01/2017 10:00:00");
date = DateTime.fromISO("2017-06-01T11:00:00+02:00");
assert.strictEqual(formatDateTime(date), "06/01/2017 10:00:00");
assert.strictEqual(formatDateTime(date, { timezone: false }), "06/01/2017 09:00:00");
});
QUnit.test("parseDateTime", async (assert) => {
@@ -223,6 +232,24 @@ QUnit.module(
unpatch(localization, "weird loc");
});
QUnit.test("parseDate", async (assert) => {
patchWithCleanup(localization, defaultLocalization);
let str = "07/21/2022";
assert.strictEqual(parseDate(str).toISO(), "2022-07-21T00:00:00.000Z");
assert.strictEqual(
parseDate(str, { timezone: true }).toISO(),
"2022-07-21T00:00:00.000+01:00"
);
str = "07/22/2022";
assert.strictEqual(parseDate(str).toISO(), "2022-07-22T00:00:00.000Z");
assert.strictEqual(
parseDate(str, { timezone: true }).toISO(),
"2022-07-22T00:00:00.000+01:00"
);
});
QUnit.test("parseDate without separator", async (assert) => {
const dateFormat = strftimeToLuxonFormat("%d.%m/%Y");
const timeFormat = strftimeToLuxonFormat("%H:%M:%S");
@@ -373,6 +400,24 @@ QUnit.module(
assert.strictEqual(serializeDate(date), "2022-02-21");
});
QUnit.test("serializeDate, with DateTime.now()", async (assert) => {
patchDate(2022, 1, 21, 16, 11, 42);
const date = DateTime.now();
assert.strictEqual(date.toFormat("yyyy-MM-dd"), "2022-02-21");
assert.strictEqual(serializeDate(date), "2022-02-21");
});
QUnit.test("serializeDate, with DateTime.now(), midnight", async (assert) => {
patchDate(2022, 1, 21, 0, 0, 0);
const date = DateTime.now();
assert.strictEqual(date.toFormat("yyyy-MM-dd"), "2022-02-21");
assert.strictEqual(
serializeDate(date),
"2022-02-20",
"serializeDate should output an UTC converted string"
);
});
QUnit.test("serializeDate with different numbering system", async (assert) => {
patchWithCleanup(Settings, { defaultNumberingSystem: "arab" });
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);
@@ -386,6 +431,28 @@ QUnit.module(
assert.strictEqual(serializeDateTime(date), "2022-02-21 16:11:42");
});
QUnit.test("serializeDateTime, with DateTime.now()", async (assert) => {
patchDate(2022, 1, 21, 16, 11, 42);
const date = DateTime.now();
assert.strictEqual(date.toFormat("yyyy-MM-dd HH:mm:ss"), "2022-02-21 16:11:42");
assert.strictEqual(
serializeDateTime(date),
"2022-02-21 15:11:42",
"serializeDateTime should output an UTC converted string"
);
});
QUnit.test("serializeDateTime, with DateTime.now(), midnight", async (assert) => {
patchDate(2022, 1, 21, 0, 0, 0);
const date = DateTime.now();
assert.strictEqual(date.toFormat("yyyy-MM-dd HH:mm:ss"), "2022-02-21 00:00:00");
assert.strictEqual(
serializeDateTime(date),
"2022-02-20 23:00:00",
"serializeDateTime should output an UTC converted string"
);
});
QUnit.test("serializeDateTime with different numbering system", async (assert) => {
patchWithCleanup(Settings, { defaultNumberingSystem: "arab" });
const date = DateTime.utc(2022, 2, 21, 16, 11, 42);