[REF] *:Replace moment usages with luxon

luxon and moment are both used in the solution, but
these two libraries facilitate the manipulation of dates.
It was decided to replace all uses of moment with
luxon so we can then remove moment.js from the
code and lighten the assets.

task-3391739

closes odoo/odoo#127406

Signed-off-by: Mathieu Duckerts-Antoine (dam) <dam@odoo.com>
This commit is contained in:
Pierre Pulinckx (pipu)
2023-07-13 14:47:41 +02:00
parent 5b5699e0c1
commit 504b4a7178
18 changed files with 110 additions and 89 deletions
@@ -4,6 +4,9 @@ import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { ListController } from "@web/views/list/list_controller";
import { serializeDate } from "@web/core/l10n/dates";
const { DateTime } = luxon;
QUnit.module("activity widget");
@@ -14,17 +17,18 @@ QUnit.test("list activity widget: reschedule button in dropdown", async (assert)
icon: "fa-calendar",
name: "Meeting",
});
const tomorrow = serializeDate(DateTime.now().plus({days:1}));
const attendeeId = pyEnv["calendar.attendee"].create({ partner_id: resPartnerId });
const meetingId = pyEnv["calendar.event"].create({
res_model: "calendar.event",
name: "meeting1",
start: moment().add(1, "day").format("YYYY-MM-DD"), // tomorrow
start: tomorrow,
attendee_ids: [attendeeId],
});
const activityId_1 = pyEnv["mail.activity"].create({
name: "OXP",
activity_type_id: activityTypeId,
date_deadline: moment().add(1, "day").format("YYYY-MM-DD"), // tomorrow
date_deadline: tomorrow,
state: "planned",
can_write: true,
res_id: resPartnerId,
-9
View File
@@ -1,6 +1,5 @@
/* @odoo-module */
import { _t } from "@web/core/l10n/translation";
import { escape } from "@web/core/utils/strings";
/**
@@ -213,13 +212,6 @@ function getTextToHTML(text) {
.replace(/[\n\r]/g, "<br/>");
}
function timeFromNow(date) {
if (moment().diff(date, "seconds") < 45) {
return _t("now");
}
return date.fromNow();
}
export {
addLink,
escapeAndCompactTextContent,
@@ -230,5 +222,4 @@ export {
parseAndTransform,
parseEmail,
stripHTML,
timeFromNow,
};
@@ -1,8 +1,7 @@
/* @odoo-module */
import { start } from "@mail/../tests/helpers/test_utils";
import { str_to_datetime } from "web.time";
import { serializeDateTime, deserializeDateTime } from "@web/core/l10n/dates";
QUnit.module("message model test", {});
@@ -25,7 +24,7 @@ QUnit.test("Message model properties", async (assert) => {
],
author: { id: 5, displayName: "Demo" },
body: "<p>Test</p>",
date: moment(str_to_datetime("2019-05-05 10:00:00")),
date: deserializeDateTime("2019-05-05 10:00:00"),
id: 4000,
needaction_partner_ids: [3],
starred_partner_ids: [3],
@@ -37,7 +36,7 @@ QUnit.test("Message model properties", async (assert) => {
assert.ok(message.isNeedaction);
assert.strictEqual(message.body, "<p>Test</p>");
assert.strictEqual(
moment(message.date).utc().format("YYYY-MM-DD hh:mm:ss"),
serializeDateTime(message.date),
"2019-05-05 10:00:00"
);
assert.strictEqual(message.id, 4000);
@@ -3,6 +3,9 @@
import { sortBy } from "@web/core/utils/arrays";
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
import { serializeDate } from "@web/core/l10n/dates";
const { DateTime} = luxon;
patch(MockServer.prototype, "mail/models/mail_activity", {
async _performRPC(route, args) {
@@ -144,7 +147,7 @@ patch(MockServer.prototype, "mail/models/mail_activity", {
group.date_deadline = minDate;
resIdToDeadline[group.res_id] = minDate;
let state;
if (group.date_deadline === moment().format("YYYY-MM-DD")) {
if (group.date_deadline === serializeDate(DateTime.now())) {
state = "today";
} else if (moment(group.date_deadline) > moment()) {
state = "planned";
@@ -5,6 +5,9 @@ import { ROUTES_TO_IGNORE } from "@mail/../tests/helpers/webclient_setup";
import { ListController } from "@web/views/list/list_controller";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { serializeDate } from "@web/core/l10n/dates";
const { DateTime } = luxon;
QUnit.module("activity widget");
@@ -148,7 +151,7 @@ QUnit.test("list activity widget: open dropdown", async (assert) => {
const [activityId_1, activityId_2] = pyEnv["mail.activity"].create([
{
display_name: "Call with Al",
date_deadline: moment().format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now()), // now
can_write: true,
state: "today",
user_id: pyEnv.currentUserId,
@@ -157,7 +160,7 @@ QUnit.test("list activity widget: open dropdown", async (assert) => {
},
{
display_name: "Meet FP",
date_deadline: moment().add(1, "day").format("YYYY-MM-DD"), // tomorrow
date_deadline: serializeDate(DateTime.now().plus({days:1})), // tomorrow
can_write: true,
state: "planned",
user_id: pyEnv.currentUserId,
@@ -237,7 +240,7 @@ QUnit.test("list activity exception widget with activity", async (assert) => {
const [activityId_1, activityId_2] = pyEnv["mail.activity"].create([
{
display_name: "An activity",
date_deadline: moment().format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now()), // now
can_write: true,
state: "today",
user_id: pyEnv.currentUserId,
@@ -246,7 +249,7 @@ QUnit.test("list activity exception widget with activity", async (assert) => {
},
{
display_name: "An exception activity",
date_deadline: moment().format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now()), // now
can_write: true,
state: "today",
user_id: pyEnv.currentUserId,
@@ -5,7 +5,7 @@
import {_t} from "web.core";
import "web.legacy_tranlations_loaded";
import {Markup} from "web.utils";
var now = moment();
const { DateTime } = luxon;
registry.category("web_tour.tours").add('mass_mailing_tour', {
url: '/web',
@@ -30,7 +30,7 @@
trigger: 'input[name="subject"]',
content: Markup(_t('Pick the <b>email subject</b>.')),
position: 'bottom',
run: 'text ' + now.format("MMMM") + " Newsletter",
run: 'text ' + DateTime.now().toFormat("LLLL") + " Newsletter",
}, {
trigger: 'div[name="contact_list_ids"] > .o_input_dropdown > input[type="text"]',
run: 'click',
@@ -9,6 +9,8 @@ import { useBus, useService } from "@web/core/utils/hooks";
import { useEffect, useRef, useState, Component } from "@odoo/owl";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { serializeDateTime } from "@web/core/l10n/dates";
const { DateTime } = luxon;
export class DebugWidget extends Component {
static components = { Transition };
@@ -130,7 +132,7 @@ export class DebugWidget extends Component {
}
}
get paidOrdersFilename() {
return `${this.env._t("paid orders")} ${moment().format("YYYY-MM-DD-HH-mm-ss")}.json`;
return `${this.env._t("paid orders")} ${serializeDateTime(DateTime.now()).replace(/:|\s/gi, "-")}.json`;
}
get paidOrdersURL() {
var URL = window.URL || window.webkitURL;
@@ -146,7 +148,7 @@ export class DebugWidget extends Component {
}
}
get unpaidOrdersFilename() {
return `${this.env._t("unpaid orders")} ${moment().format("YYYY-MM-DD-HH-mm-ss")}.json`;
return `${this.env._t("unpaid orders")} ${serializeDateTime(DateTime.now()).replace(/:|\s/gi, "-")}.json`;
}
get unpaidOrdersURL() {
var URL = window.URL || window.webkitURL;
@@ -3,6 +3,8 @@
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { _lt } from "@web/core/l10n/translation";
import { usePos } from "@point_of_sale/app/store/pos_hook";
import { serializeDateTime } from "@web/core/l10n/dates";
const { DateTime } = luxon;
// formerly ErrorTracebackPopupWidget
export class ErrorTracebackPopup extends ErrorPopup {
@@ -27,7 +29,7 @@ export class ErrorTracebackPopup extends ErrorPopup {
return URL.createObjectURL(blob);
}
get tracebackFilename() {
return `${this.env._t("error")} ${moment().format("YYYY-MM-DD-HH-mm-ss")}.txt`;
return `${this.env._t("error")} ${serializeDateTime(DateTime.now()).replace(/:|\s/gi, "-")}.txt`;
}
emailTraceback() {
const address = this.pos.company.email;
@@ -2,9 +2,11 @@
import core from "web.core";
import publicWidget from "web.public.widget";
import time from "web.time";
import session from "web.session";
import { sprintf } from "@web/core/utils/strings";
import { deserializeDateTime } from "@web/core/l10n/dates";
const { DateTime } = luxon;
var _t = core._t;
@@ -30,9 +32,9 @@ var PortalSidebar = publicWidget.Widget.extend({
_setDelayLabel: function () {
var $sidebarTimeago = this.$el.find('.o_portal_sidebar_timeago').toArray();
$sidebarTimeago.forEach((el) => {
var dateTime = moment(time.auto_str_to_date($(el).attr('datetime'))),
today = moment().startOf('day'),
diff = dateTime.diff(today, 'days', true),
var dateTime = deserializeDateTime($(el).attr('datetime')),
today = DateTime.now().startOf('day'),
diff = dateTime.diff(today).as("days"),
displayStr;
session.is_bound.then(function (){
@@ -4,6 +4,7 @@ import { _t } from "@web/core/l10n/translation";
import { PaymentInterface } from "@point_of_sale/app/payment/payment_interface";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { sprintf } from "@web/core/utils/strings";
const { DateTime } = luxon;
export class PaymentAdyen extends PaymentInterface {
send_payment_request(cid) {
@@ -96,7 +97,7 @@ export class PaymentAdyen extends PaymentInterface {
SaleData: {
SaleTransactionID: {
TransactionID: order.uid,
TimeStamp: moment().format(), // iso format: '2018-01-10T11:30:15+00:00'
TimeStamp: DateTime.now().toFormat("yyyy-MM-dd'T'HH:mm:ssZZ"), // iso format: '2018-01-10T11:30:15+00:00'
},
},
PaymentTransaction: {
@@ -31,6 +31,8 @@ import {
} from "@spreadsheet/../tests/utils/date_domain";
import GlobalFiltersUIPlugin from "@spreadsheet/global_filters/plugins/global_filters_ui_plugin";
import { migrate } from "@spreadsheet/o_spreadsheet/migration";
const { DateTime } = luxon;
/**
* @typedef {import("@spreadsheet/global_filters/plugins/global_filters_core_plugin").GlobalFilter} GlobalFilter
@@ -604,7 +606,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
},
});
await nextTick();
assert.equal(getCellValue(model, "A10"), `Q1/${moment().year()}`);
assert.equal(getCellValue(model, "A10"), `Q1/${DateTime.now().year}`);
await setGlobalFilterValue(model, {
id: filter.id,
rangeType: "year",
@@ -613,7 +615,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
},
});
await nextTick();
assert.equal(getCellValue(model, "A10"), `${moment().year()}`);
assert.equal(getCellValue(model, "A10"), `${DateTime.now().year}`);
await setGlobalFilterValue(model, {
id: filter.id,
rangeType: "year",
@@ -623,7 +625,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
},
});
await nextTick();
assert.equal(getCellValue(model, "A10"), `01/${moment().year()}`);
assert.equal(getCellValue(model, "A10"), `01/${DateTime.now().year}`);
await setGlobalFilterValue(model, {
id: filter.id,
rangeType: "year",
@@ -3,6 +3,7 @@
import { ActivityModel } from "@mail/views/web/activity/activity_model";
import { ActivityRenderer } from "@mail/views/web/activity/activity_renderer";
import { start, startServer } from "@mail/../tests/helpers/test_utils";
import { serializeDate } from "@web/core/l10n/dates";
import testUtils from "web.test_utils";
@@ -16,6 +17,7 @@ import {
import { doAction } from "@web/../tests/webclient/helpers";
import { session } from "@web/session";
import { toggleSearchBarMenu } from "@web/../tests/search/helpers";
const { DateTime } = luxon;
let serverData;
let pyEnv;
@@ -41,7 +43,7 @@ QUnit.module("test_mail", {}, function () {
const mailActivityIds = pyEnv["mail.activity"].create([
{
display_name: "An activity",
date_deadline: moment().add(3, "days").format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now().plus({days:3})),
can_write: true,
state: "planned",
activity_type_id: mailActivityTypeIds[0],
@@ -50,7 +52,7 @@ QUnit.module("test_mail", {}, function () {
},
{
display_name: "An activity",
date_deadline: moment().format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now()),
can_write: true,
state: "today",
activity_type_id: mailActivityTypeIds[0],
@@ -60,7 +62,7 @@ QUnit.module("test_mail", {}, function () {
{
res_model: "mail.test.activity",
display_name: "An activity",
date_deadline: moment().subtract(2, "days").format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now().minus({days:2})),
can_write: true,
state: "overdue",
activity_type_id: mailActivityTypeIds[1],
@@ -220,7 +222,7 @@ QUnit.module("test_mail", {}, function () {
for (let i = 0; i < 81; i++) {
activityToCreate.push({
display_name: "An activity " + i,
date_deadline: moment().add(3, "days").format("YYYY-MM-DD"),
date_deadline: serializeDate(DateTime.now().plus({days:3})),
can_write: true,
state: "planned",
activity_type_id: mailActivityTypeIds[0],
@@ -822,7 +824,7 @@ QUnit.module("test_mail", {}, function () {
pyEnv["mail.activity"].create([
{
display_name: "An activity",
date_deadline: moment().add(3, "days").format("YYYY-MM-DD"), // now
date_deadline: serializeDate(DateTime.now().plus({days:3})),
can_write: true,
state: "planned",
activity_type_id: mailActivityTypeIds[2],
@@ -20,6 +20,7 @@ import dom from "web.dom";
import session from "web.session";
import time from "web.time";
import utils from "web.utils";
const { DateTime } = luxon;
var _t = core._t;
@@ -443,7 +444,7 @@ function formatJson(value, field, options) {
* "-4y" will return now + 4 years
*
* @param {string} value
* @returns {Moment|false} Moment date object
* @returns {DateTime|false} luxon DateTime
*/
function parseSmartDateInput(value) {
const units = {
@@ -455,13 +456,13 @@ function parseSmartDateInput(value) {
const re = new RegExp(`^([+-])(\\d+)([${Object.keys(units).join('')}]?)$`);
const match = re.exec(value);
if (match) {
let date = moment();
let date = DateTime.now()
const offset = parseInt(match[2], 10);
const unit = units[match[3] || 'd'];
if (match[1] === '+') {
date.add(offset, unit);
date.plus({[unit]: offset});
} else {
date.subtract(offset, unit);
date.minus({[unit]: offset});
}
return date;
}
@@ -5,6 +5,13 @@ import { pick } from "@web/core/utils/objects";
import Class from "web.Class";
import Domain from "web.Domain";
import pyUtils from "web.py_utils";
import {
parseDateTime,
serializeDate,
serializeDateTime,
deserializeDate,
deserializeDateTime
} from "@web/core/l10n/dates";
var MockServer = Class.extend({
/**
@@ -62,8 +69,6 @@ var MockServer = Class.extend({
}
this.debug = options.debug;
this.currentDate = options.currentDate || moment().format("YYYY-MM-DD");
this.actions = options.actions || [];
this.archs = options.archs || {};
},
@@ -1475,30 +1480,32 @@ var MockServer = Class.extend({
const [fieldName, aggregateFunction = "month"] = groupByField.split(':');
const { type } = fields[fieldName];
if (type === "date") {
if (aggregateFunction === 'day') {
return moment(val).format('YYYY-MM-DD');
} else if (aggregateFunction === 'week') {
return moment(val).format('[W]WW GGGG');
} else if (aggregateFunction === 'quarter') {
return moment(val).format('[Q]Q YYYY');
} else if (aggregateFunction === 'year') {
return moment(val).format('Y');
const date = deserializeDate(val);
if (aggregateFunction === "day") {
return date.toFormat("yyyy-MM-dd");
} else if (aggregateFunction === "week") {
return `W${date.toFormat("WW kkkk")}`;
} else if (aggregateFunction === "quarter") {
return `Q${date.toFormat("q yyyy")}`;
} else if (aggregateFunction === "year") {
return date.toFormat("yyyy");
} else {
return moment(val).format('MMMM YYYY');
return date.toFormat("MMMM yyyy");
}
} else if (type === "datetime") {
const date = deserializeDateTime(val);
if (aggregateFunction === 'hour') {
return moment(val).format('HH[:00] DD MMM');
} else if (aggregateFunction === 'day') {
return moment(val).format('YYYY-MM-DD');
} else if (aggregateFunction === 'week') {
return moment(val).format('[W]WW GGGG');
} else if (aggregateFunction === 'quarter') {
return moment(val).format('[Q]Q YYYY');
} else if (aggregateFunction === 'year') {
return moment(val).format('Y');
return date.toFormat("HH:00 dd MMM yyyy");
} else if (aggregateFunction === "day") {
return date.toFormat("yyyy-MM-dd");
} else if (aggregateFunction === "week") {
return `W${date.toFormat("WW kkkk")}`;
} else if (aggregateFunction === "quarter") {
return `Q${date.toFormat("q yyyy")}`;
} else if (aggregateFunction === "year") {
return date.toFormat("yyyy");
} else {
return moment(val).format('MMMM YYYY');
return date.toFormat("MMMM yyyy");
}
} else if (Array.isArray(val)) {
if (val.length === 0) {
@@ -1578,43 +1585,43 @@ var MockServer = Class.extend({
let startDate, endDate;
switch (dateRange) {
case "hour": {
startDate = moment(value, "HH[:00] DD MMM");
endDate = startDate.clone().add(1, "hours");
startDate = parseDateTime(value, { format: "HH:00 dd MMM yyyy" });
group[gbField] = startDate.toFormat("HH:00 dd MMM");
endDate = startDate.plus({ hours: 1 });
// Remove the year from the result value of the group. It was needed
// to compute the startDate and endDate.
break;
}
case "day": {
startDate = moment(value, "YYYY-MM-DD");
endDate = startDate.clone().add(1, "days");
startDate = parseDateTime(value, { format: "yyyy-MM-dd" });
endDate = startDate.plus({ days: 1 });
break;
}
case "week": {
startDate = moment(value, "[W]WW GGGG");
endDate = startDate.clone().add(1, "weeks");
startDate = parseDateTime(value, { format: "WW kkkk" });
endDate = startDate.plus({ weeks: 1 });
break;
}
case "quarter": {
startDate = moment(value, "[Q]Q YYYY");
endDate = startDate.clone().add(1, "quarters");
startDate = parseDateTime(value, { format: "q yyyy" });
endDate = startDate.plus({ quarters: 1 });
break;
}
case "year": {
startDate = moment(value, "Y");
endDate = startDate.clone().add(1, "years");
startDate = parseDateTime(value, { format: "y" });
endDate = startDate.plus({ years: 1 });
break;
}
case "month":
default: {
startDate = moment(value, "MMMM YYYY");
endDate = startDate.clone().add(1, "months");
startDate = parseDateTime(value, { format: "MMMM yyyy" });
endDate = startDate.plus({ months: 1 });
break;
}
}
const from = type === "date"
? startDate.format("YYYY-MM-DD")
: startDate.format("YYYY-MM-DD HH:mm:ss");
const to = type === "date"
? endDate.format("YYYY-MM-DD")
: endDate.format("YYYY-MM-DD HH:mm:ss");
const serialize = type === "date" ? serializeDate : serializeDateTime;
const from = serialize(startDate);
const to = serialize(endDate);
// NOTE THAT the range and the domain computed here are not really accurate
// due to a the timezone not really taken into account.
// FYI, the non legacy version of the mock server handles this correctly.
@@ -1,6 +1,7 @@
/** @odoo-module **/
import testUtils from "web.test_utils";
const { DateTime } = luxon;
QUnit.module('web', {}, function () {
QUnit.module('testUtils', {}, function () {
@@ -25,8 +26,7 @@ QUnit.test('new moment', function (assert) {
assert.expect(1);
const unpatchDate = testUtils.mock.patchDate(2018, 9, 23, 14, 50, 0);
const m = moment();
assert.strictEqual(m.format('YYYY-MM-DD HH:mm'), '2018-10-23 14:50');
assert.strictEqual(DateTime.now().toFormat("yyyy-MM-dd HH:mm"), '2018-10-23 14:50');
unpatchDate();
});
@@ -299,7 +299,7 @@ QUnit.module("Legacy MockServer", {
});
assert.deepEqual(
result.map((x) => x["datetime:hour"]),
["12:00 14 Dec", "12:00 26 Oct", "12:00 15 Dec", "12:00 11 Apr", "12:00 30 Dec"]
["13:00 14 Dec", "13:00 26 Oct", "13:00 15 Dec", "13:00 11 Apr", "13:00 30 Dec"]
);
assert.deepEqual(
result.map((x) => x.datetime_count),
@@ -2,6 +2,7 @@
import publicWidget from "web.public.widget";
const { DateTime } = luxon;
/*
* Simple implementation of a timer widget that uses a "time to live" configuration
@@ -22,8 +23,8 @@ publicWidget.registry.websiteEventTrackTimer = publicWidget.Widget.extend({
start: function () {
return this._super.apply(this, arguments).then(() => {
let timeToLive = this.$el.data('time-to-live');
let deadline = moment().add(timeToLive, 'seconds');
let remainingMs = deadline.diff(moment());
let deadline = DateTime.now().plus({seconds : timeToLive});
let remainingMs = deadline.diff(DateTime.now()).as("milliseconds");
if (remainingMs > 0) {
this._updateTimerDisplay(remainingMs);
this.$el.removeClass('d-none');
@@ -57,7 +58,7 @@ publicWidget.registry.websiteEventTrackTimer = publicWidget.Widget.extend({
* Otherwise, the component will be destroyed.
*/
_refreshTimer: function () {
let remainingMs = this.deadline.diff(moment());
let remainingMs = this.deadline.diff(DateTime.now()).as("milliseconds");
if (remainingMs > 0) {
this._updateTimerDisplay(remainingMs);
} else {
@@ -2,6 +2,7 @@
import publicWidget from 'web.public.widget';
import time from 'web.time';
const { DateTime } = luxon;
publicWidget.registry.websiteSlides = publicWidget.Widget.extend({
selector: '#wrapwrap',
@@ -20,9 +21,9 @@ publicWidget.registry.websiteSlides = publicWidget.Widget.extend({
// then return fix formate string else timeago
var displayStr = '';
if (datetimeObj && new Date().getTime() - datetimeObj.getTime() > 7 * 24 * 60 * 60 * 1000) {
displayStr = moment(datetimeObj).format('ll');
displayStr = DateTime.fromJSDate(datetimeObj).toFormat('DD');
} else {
displayStr = moment(datetimeObj).fromNow();
displayStr = DateTime.fromJSDate(datetimeObj).toRelative();
}
$(el).text(displayStr);
});