diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts index 9dd4aee20358c..0fd18aaa082e2 100644 --- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts +++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts @@ -1,5 +1,5 @@ import { LivechatContacts, Statistics, Users } from '@rocket.chat/models'; -import moment from 'moment'; +import { format } from 'date-fns'; import { settings } from '../../../settings/server'; import { statistics } from '../../../statistics/server'; @@ -69,7 +69,7 @@ export async function buildWorkspaceRegistrationData('Server_Type'); const seats = await Users.getActiveLocalUserCount(); - const MAC = await LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM')); + const MAC = await LivechatContacts.countContactsOnPeriod(format(new Date(), 'yyyy-MM')); const license = settings.get('Enterprise_License'); diff --git a/apps/meteor/app/irc/server/irc-bridge/index.js b/apps/meteor/app/irc/server/irc-bridge/index.js index 25f6b5b9f0a29..a3bcfeb58df16 100644 --- a/apps/meteor/app/irc/server/irc-bridge/index.js +++ b/apps/meteor/app/irc/server/irc-bridge/index.js @@ -1,6 +1,5 @@ import { Logger } from '@rocket.chat/logger'; import { Settings } from '@rocket.chat/models'; -import moment from 'moment'; import Queue from 'queue-fifo'; import { withThrottling } from '../../../../lib/utils/highOrderFunctions'; @@ -60,7 +59,7 @@ class Bridge { const lastPing = await Settings.findOneById('IRC_Bridge_Last_Ping'); if (lastPing) { - if (Math.abs(moment(lastPing.value).diff()) < 1000 * 30) { + if (Math.abs(new Date(lastPing.value).getTime() - Date.now()) < 1000 * 30) { this.log('Not trying to connect.'); this.remove(); return; diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 80cf172c9dcb1..b05fcc3f3bfc4 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -2,7 +2,6 @@ import type { IMessage, IRoom, IUser, RoomType } from '@rocket.chat/core-typings import { isEditedMessage } from '@rocket.chat/core-typings'; import type { Updater } from '@rocket.chat/models'; import { Subscriptions, Rooms } from '@rocket.chat/models'; -import moment from 'moment'; import { notifyOnSubscriptionChanged, @@ -165,7 +164,7 @@ export async function updateThreadUsersSubscriptions(message: IMessage, replies: export async function notifyUsersOnMessage(message: IMessage, room: IRoom, roomUpdater: Updater): Promise { // Skips this callback if the message was edited and increments it if the edit was way in the past (aka imported) if (isEditedMessage(message)) { - if (Math.abs(moment(message.editedAt).diff(Date.now())) > 60000) { + if (Math.abs(new Date(message.editedAt).getTime() - Date.now()) > 60000) { // TODO: Review as I am not sure how else to get around this as the incrementing of the msgs count shouldn't be in this callback Rooms.getIncMsgCountUpdateQuery(1, roomUpdater); return message; @@ -183,7 +182,7 @@ export async function notifyUsersOnMessage(message: IMessage, room: IRoom, roomU return message; } - if (message.ts && Math.abs(moment(message.ts).diff(Date.now())) > 60000) { + if (message.ts && Math.abs(new Date(message.ts).getTime() - Date.now()) > 60000) { Rooms.getIncMsgCountUpdateQuery(1, roomUpdater); return message; } diff --git a/apps/meteor/app/lib/server/lib/processDirectEmail.ts b/apps/meteor/app/lib/server/lib/processDirectEmail.ts index e930add5716e8..42ca66fb31dac 100644 --- a/apps/meteor/app/lib/server/lib/processDirectEmail.ts +++ b/apps/meteor/app/lib/server/lib/processDirectEmail.ts @@ -1,7 +1,6 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { Messages, Subscriptions, Users, Rooms } from '@rocket.chat/models'; import type { ParsedMail } from 'mailparser'; -import moment from 'moment'; import { canAccessRoomAsync } from '../../../authorization/server'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -25,7 +24,7 @@ export const processDirectEmail = async function (email: ParsedMail): Promise 60000) { + if (message.ts && Math.abs(new Date(message.ts).getTime() - Date.now()) > 60000) { return message; } diff --git a/apps/meteor/app/lib/server/methods/sendMessage.ts b/apps/meteor/app/lib/server/methods/sendMessage.ts index cf5deabc68a66..3782bd1e49b51 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.ts +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -7,7 +7,6 @@ import { Messages, Users } from '@rocket.chat/models'; import type { TOptions } from 'i18next'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import moment from 'moment'; import { i18n } from '../../../../server/lib/i18n'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -49,7 +48,7 @@ export async function executeSendMessage( const now = new Date(); message.ts = extraInfo?.ts ?? message.ts ?? now; if (isTimestampFromClient) { - const tsDiff = Math.abs(moment(message.ts).diff(Date.now())); + const tsDiff = Math.abs(new Date(message.ts).getTime() - Date.now()); if (tsDiff > 60000) { throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', { method: 'sendMessage', diff --git a/apps/meteor/app/lib/server/methods/updateMessage.ts b/apps/meteor/app/lib/server/methods/updateMessage.ts index 833b4403c0eca..3021ba1966eeb 100644 --- a/apps/meteor/app/lib/server/methods/updateMessage.ts +++ b/apps/meteor/app/lib/server/methods/updateMessage.ts @@ -1,9 +1,9 @@ import type { IEditedMessage, IMessage, IUser, AtLeast } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Users } from '@rocket.chat/models'; +import { differenceInMinutes } from 'date-fns'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import moment from 'moment'; import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -65,13 +65,9 @@ export async function executeUpdateMessage( if (!bypassBlockTimeLimit && Match.test(blockEditInMinutes, Number) && blockEditInMinutes !== 0) { let currentTsDiff = 0; - let msgTs; if (originalMessage.ts instanceof Date || Match.test(originalMessage.ts, Number)) { - msgTs = moment(originalMessage.ts); - } - if (msgTs) { - currentTsDiff = moment().diff(msgTs, 'minutes'); + currentTsDiff = differenceInMinutes(new Date(), originalMessage.ts); } if (currentTsDiff >= blockEditInMinutes) { throw new Meteor.Error('error-message-editing-blocked', 'Message editing is blocked', { diff --git a/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts b/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts index 2ebeae1b77a0f..3cfa8f967a037 100644 --- a/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts +++ b/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts @@ -4,7 +4,6 @@ import { isDirectMessageRoom, isEditedMessage, isOmnichannelRoom, isRoomFederate import { Subscriptions, Users } from '@rocket.chat/models'; import { isTruthy } from '@rocket.chat/tools'; import type { ActionsBlock } from '@rocket.chat/ui-kit'; -import moment from 'moment'; import { callbacks } from '../../../../server/lib/callbacks'; import { i18n } from '../../../../server/lib/i18n'; @@ -57,7 +56,7 @@ callbacks.add( // TODO: check if I need to test this 60 second rule. // If the message was edited, or is older than 60 seconds (imported) // the notifications will be skipped, so we can also skip this validation - if (isEditedMessage(message) || (message.ts && Math.abs(moment(message.ts).diff(moment())) > 60000) || !message.mentions) { + if (isEditedMessage(message) || (message.ts && Math.abs(new Date(message.ts).getTime() - Date.now()) > 60000) || !message.mentions) { return message; } diff --git a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts index fbda30475d5c2..6a15fb85cd793 100644 --- a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts +++ b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts @@ -2,7 +2,7 @@ import type { IOmnichannelRoom, IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage, isMessageFromVisitor, isSystemMessage } from '@rocket.chat/core-typings'; import type { Updater } from '@rocket.chat/models'; import { LivechatRooms, LivechatContacts, LivechatInquiry } from '@rocket.chat/models'; -import moment from 'moment'; +import { format } from 'date-fns'; import { callbacks } from '../../../../server/lib/callbacks'; import { notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener'; @@ -23,7 +23,7 @@ export async function markRoomResponded( return; } - const monthYear = moment().format('YYYY-MM'); + const monthYear = format(new Date(), 'yyyy-MM'); const isContactActive = await LivechatContacts.isContactActiveOnPeriod({ visitorId: room.v._id, source: room.source }, monthYear); // Case: agent answers & visitor is not active, we mark visitor as active diff --git a/apps/meteor/app/smarsh-connector/server/functions/generateEml.ts b/apps/meteor/app/smarsh-connector/server/functions/generateEml.ts index ea9525442e469..fb318014ad426 100644 --- a/apps/meteor/app/smarsh-connector/server/functions/generateEml.ts +++ b/apps/meteor/app/smarsh-connector/server/functions/generateEml.ts @@ -1,7 +1,7 @@ import { MessageTypes } from '@rocket.chat/message-types'; import { Messages, SmarshHistory, Users, Rooms } from '@rocket.chat/models'; +import { differenceInMinutes } from 'date-fns'; import { Meteor } from 'meteor/meteor'; -import moment from 'moment-timezone'; import { sendEmail } from './sendEmail'; import { i18n } from '../../../../server/lib/i18n'; @@ -51,7 +51,7 @@ export const generateEml = async (): Promise => { users: [], msgs: 0, files: [], - time: smarshHistory ? moment(date).diff(moment(smarshHistory.lastRan), 'minutes') : moment(date).diff(moment(room.ts), 'minutes'), + time: smarshHistory ? differenceInMinutes(date, smarshHistory.lastRan) : differenceInMinutes(date, room.ts ?? new Date()), room: room.name ? `#${room.name}` : `Direct Message Between: ${room?.usernames?.join(' & ')}`, }; @@ -62,7 +62,22 @@ export const generateEml = async (): Promise => { // The timestamp rows.push(open20td); - rows.push(moment(message.ts).tz(timeZone).format('YYYY-MM-DD HH-mm-ss z')); + rows.push( + new Intl.DateTimeFormat('en-US', { + timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + hour12: false, + }) + .format(new Date(message.ts)) + .replace(/\//g, '-') + .replace(/, /g, ' '), + ); rows.push(closetd); // The sender diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 8c810acaad70b..df1592d4324ab 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -28,8 +28,8 @@ import { LivechatRooms, AbacAttributes, } from '@rocket.chat/models'; +import { format, subDays } from 'date-fns'; import { MongoInternals } from 'meteor/mongo'; -import moment from 'moment'; import { getAppsStatistics } from './getAppsStatistics'; import { getContactVerificationStatistics } from './getContactVerificationStatistics'; @@ -246,29 +246,29 @@ export const statistics = { ); const defaultValue = { contactsCount: 0, conversationsCount: 0, sources: [] }; - const billablePeriod = moment.utc().format('YYYY-MM'); + const billablePeriod = format(new Date(), 'yyyy-MM'); statsPms.push( LivechatRooms.getMACStatisticsForPeriod(billablePeriod).then(([result]) => { statistics.omnichannelContactsBySource = result || defaultValue; }), ); - const monthAgo = moment.utc().subtract(30, 'days').toDate(); - const today = moment.utc().toDate(); + const today = new Date(); + const monthAgo = subDays(today, 30); statsPms.push( LivechatRooms.getMACStatisticsBetweenDates(monthAgo, today).then(([result]) => { statistics.uniqueContactsOfLastMonth = result || defaultValue; }), ); - const weekAgo = moment.utc().subtract(7, 'days').toDate(); + const weekAgo = subDays(today, 7); statsPms.push( LivechatRooms.getMACStatisticsBetweenDates(weekAgo, today).then(([result]) => { statistics.uniqueContactsOfLastWeek = result || defaultValue; }), ); - const yesterday = moment.utc().subtract(1, 'days').toDate(); + const yesterday = subDays(today, 1); statsPms.push( LivechatRooms.getMACStatisticsBetweenDates(yesterday, today).then(([result]) => { statistics.uniqueContactsOfYesterday = result || defaultValue; diff --git a/apps/meteor/app/utils/server/lib/getTimezone.ts b/apps/meteor/app/utils/server/lib/getTimezone.ts index 8cbc5056783da..5674c8ccb780d 100644 --- a/apps/meteor/app/utils/server/lib/getTimezone.ts +++ b/apps/meteor/app/utils/server/lib/getTimezone.ts @@ -1,23 +1,7 @@ -import moment from 'moment-timezone'; +import { guessTimezone, guessTimezoneFromOffset } from '@rocket.chat/tools'; import { settings } from '../../../settings/server'; -const padOffset = (offset: string | number): string => { - const numberOffset = Number(offset); - const absOffset = Math.abs(numberOffset); - const isNegative = !(numberOffset === absOffset); - - return `${isNegative ? '-' : '+'}${absOffset < 10 ? `0${absOffset}` : absOffset}:00`; -}; - -const guessTimezoneFromOffset = (offset?: string | number): string => { - if (!offset) { - return moment.tz.guess(); - } - - return moment.tz.names().find((tz) => padOffset(offset) === moment.tz(tz).format('Z').toString()) || moment.tz.guess(); -}; - export const getTimezone = (user?: { utcOffset?: string | number } | null): string => { const timezone = settings.get('Default_Timezone_For_Reporting'); @@ -25,8 +9,8 @@ export const getTimezone = (user?: { utcOffset?: string | number } | null): stri case 'custom': return settings.get('Default_Custom_Timezone'); case 'user': - return guessTimezoneFromOffset(user?.utcOffset); + return user?.utcOffset != null ? guessTimezoneFromOffset(user.utcOffset) : guessTimezone(); default: - return moment.tz.guess(); + return guessTimezone(); } }; diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index f2e5768c96397..28c9a4ca9b6b9 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -3,7 +3,7 @@ import type { LicenseLimitKind } from '@rocket.chat/core-typings'; import { applyLicense, applyLicenseOrRemove, License } from '@rocket.chat/license'; import { Subscriptions, Users, Settings, LivechatContacts } from '@rocket.chat/models'; import { wrapExceptions } from '@rocket.chat/tools'; -import moment from 'moment'; +import { format } from 'date-fns'; import { getAppCount } from './lib/getAppCount'; import { syncWorkspace } from '../../../../app/cloud/server/functions/syncWorkspace'; @@ -112,7 +112,7 @@ export const startLicense = async () => { ); License.setLicenseLimitCounter('privateApps', () => getAppCount('private')); License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace')); - License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM'))); + License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatContacts.countContactsOnPeriod(format(new Date(), 'yyyy-MM'))); return new Promise((resolve) => { // When settings are loaded, apply the current license if there is one. diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts index e422808d19e6e..5d63e8f39963f 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts @@ -1,5 +1,5 @@ import type { ILivechatInquiryRecord } from '@rocket.chat/core-typings'; -import moment from 'moment'; +import { addMinutes } from 'date-fns'; import { afterInquiryQueued } from '../../../../../app/livechat/server/lib/hooks'; import { settings } from '../../../../../app/settings/server'; @@ -12,8 +12,8 @@ export const afterInquiryQueuedFunc = async (inquiry: ILivechatInquiryRecord) => } // schedule individual jobs instead of property for close inactivty - const newQueueTime = moment(inquiry._updatedAt).add(timer, 'minutes'); - await OmnichannelQueueInactivityMonitor.scheduleInquiry(inquiry._id, new Date(newQueueTime.format())); + const newQueueTime = addMinutes(new Date(inquiry._updatedAt), timer); + await OmnichannelQueueInactivityMonitor.scheduleInquiry(inquiry._id, newQueueTime); }; afterInquiryQueued.patch(async (originalFn: any, inquiry: ILivechatInquiryRecord) => { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts index 93ee3a28bb2fb..fd6ed1bdf18fc 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts @@ -1,6 +1,5 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage, isMessageFromVisitor } from '@rocket.chat/core-typings'; -import moment from 'moment'; import { markRoomResponded } from '../../../../../app/livechat/server/hooks/markRoomResponded'; import { settings } from '../../../../../app/settings/server'; @@ -37,7 +36,7 @@ callbacks.add( return; } - if (moment(responseBy.firstResponseTs).isSame(moment(message.ts))) { + if (new Date(responseBy.firstResponseTs).getTime() === new Date(message.ts).getTime()) { await setPredictedVisitorAbandonmentTime({ ...room, responseBy }, roomUpdater); } }, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts index 82a80e411120f..c6853f7dbef46 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts @@ -2,9 +2,9 @@ import { Agenda } from '@rocket.chat/agenda'; import type { IUser } from '@rocket.chat/core-typings'; import type { MainLogger } from '@rocket.chat/logger'; import { LivechatRooms, Users } from '@rocket.chat/models'; +import { addSeconds } from 'date-fns'; import { Meteor } from 'meteor/meteor'; import { MongoInternals } from 'meteor/mongo'; -import moment from 'moment'; import { schedulerLogger } from './logger'; import { closeRoom } from '../../../../../app/livechat/server/lib/closeRoom'; @@ -50,7 +50,7 @@ export class AutoCloseOnHoldSchedulerClass { await this.unscheduleRoom(roomId); const jobName = `${SCHEDULER_NAME}-${roomId}`; - const when = moment(new Date()).add(timeout, 's').toDate(); + const when = addSeconds(new Date(), timeout); this.scheduler.define(jobName, this.executeJob.bind(this)); await this.scheduler.schedule(when, jobName, { roomId, comment }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts index d4a1bcc89f9b4..04e34357cfdf5 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts @@ -14,7 +14,7 @@ import { LivechatInquiry, Users, } from '@rocket.chat/models'; -import moment from 'moment'; +import { addSeconds, addMinutes } from 'date-fns'; import type { Document } from 'mongodb'; import { OmnichannelQueueInactivityMonitor } from './QueueInactivityMonitor'; @@ -263,7 +263,7 @@ export const setPredictedVisitorAbandonmentTime = async ( return; } - const willBeAbandonedAt = moment(room.responseBy.firstResponseTs).add(Number(secondsToAdd), 'seconds').toDate(); + const willBeAbandonedAt = addSeconds(new Date(room.responseBy.firstResponseTs), Number(secondsToAdd)); if (roomUpdater) { await LivechatRooms.getPredictedVisitorAbandonmentByRoomIdUpdateQuery(willBeAbandonedAt, roomUpdater); } else { @@ -294,9 +294,9 @@ export const updateQueueInactivityTimeout = async () => { } await LivechatInquiry.getQueuedInquiries({ projection: { _updatedAt: 1 } }).forEach((inq) => { - const aggregatedDate = moment(inq._updatedAt).add(queueTimeout, 'minutes'); + const aggregatedDate = addMinutes(new Date(inq._updatedAt), queueTimeout); try { - void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, new Date(aggregatedDate.format())); + void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate); } catch (e) { // this will usually happen if other instance attempts to re-create a job logger.error({ err: e }); diff --git a/apps/meteor/ee/server/lib/deviceManagement/session.ts b/apps/meteor/ee/server/lib/deviceManagement/session.ts index a613fbb0a11d0..27fcc4f236b78 100644 --- a/apps/meteor/ee/server/lib/deviceManagement/session.ts +++ b/apps/meteor/ee/server/lib/deviceManagement/session.ts @@ -1,8 +1,8 @@ import type { ISocketConnection } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; +import { format } from 'date-fns'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; -import moment from 'moment'; import { UAParser } from 'ua-parser-js'; import * as Mailer from '../../../../app/mailer/server/api'; @@ -55,8 +55,7 @@ export const listenSessionLogin = () => { return; } - const dateFormat = settings.get('Message_TimeAndDateFormat'); - + const dateFormat = settings.get('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm'; const { name, username, @@ -75,7 +74,7 @@ export const listenSessionLogin = () => { }`, ipInfo: clientAddress, userAgent: '', - date: moment().format(String(dateFormat)), + date: format(new Date(), dateFormat), }; switch (device.type) { diff --git a/apps/meteor/ee/server/lib/engagementDashboard/channels.ts b/apps/meteor/ee/server/lib/engagementDashboard/channels.ts index a71d7c99b21df..0025b3e9cd8cf 100644 --- a/apps/meteor/ee/server/lib/engagementDashboard/channels.ts +++ b/apps/meteor/ee/server/lib/engagementDashboard/channels.ts @@ -1,6 +1,6 @@ import type { IDirectMessageRoom, IRoom } from '@rocket.chat/core-typings'; import { Analytics } from '@rocket.chat/models'; -import moment from 'moment'; +import { subDays } from 'date-fns'; import { convertDateToInt, diffBetweenDaysInclusive } from './date'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; @@ -33,8 +33,8 @@ export const findChannelsWithNumberOfMessages = async ({ total: number; }> => { const daysBetweenDates = diffBetweenDaysInclusive(end, start); - const endOfLastWeek = moment(start).subtract(1, 'days').toDate(); - const startOfLastWeek = moment(endOfLastWeek).subtract(daysBetweenDates, 'days').toDate(); + const endOfLastWeek = subDays(start, 1); + const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); const roomTypes = roomCoordinator.getTypesToShowOnDashboard() as Array; const aggregationResult = await Analytics.findRoomsByTypesWithNumberOfMessagesBetweenDate({ diff --git a/apps/meteor/ee/server/lib/engagementDashboard/date.ts b/apps/meteor/ee/server/lib/engagementDashboard/date.ts index b490fe254814d..52ff121e50dd0 100644 --- a/apps/meteor/ee/server/lib/engagementDashboard/date.ts +++ b/apps/meteor/ee/server/lib/engagementDashboard/date.ts @@ -1,5 +1,5 @@ +import { format, parse, differenceInDays, isValid } from 'date-fns'; import mem from 'mem'; -import moment from 'moment'; export const isDateISOString = mem( (input: string): input is string => { @@ -17,10 +17,15 @@ export const mapDateForAPI = (input: string): Date => { return new Date(Date.parse(input)); }; -export const convertDateToInt = (date: Date): number => parseInt(moment(date).clone().format('YYYYMMDD'), 10); -export const convertIntToDate = (intValue: number): Date => moment(intValue, 'YYYYMMDD').clone().toDate(); +export const convertDateToInt = (date: Date): number => { + if (!isValid(date)) { + throw new Error(`convertDateToInt: invalid date received: ${String(date)}`); + } + return parseInt(format(date, 'yyyyMMdd'), 10); +}; +export const convertIntToDate = (intValue: number): Date => parse(String(intValue), 'yyyyMMdd', new Date()); const diffBetweenDays = (start: string | number | Date, end: string | number | Date): number => - moment(new Date(start)).clone().diff(new Date(end), 'days'); + differenceInDays(new Date(start), new Date(end)); export const diffBetweenDaysInclusive = (start: string | number | Date, end: string | number | Date): number => diffBetweenDays(start, end) + 1; diff --git a/apps/meteor/ee/server/lib/engagementDashboard/messages.ts b/apps/meteor/ee/server/lib/engagementDashboard/messages.ts index 55bcae0da3aeb..bffa564c8db97 100644 --- a/apps/meteor/ee/server/lib/engagementDashboard/messages.ts +++ b/apps/meteor/ee/server/lib/engagementDashboard/messages.ts @@ -1,6 +1,6 @@ import type { IDirectMessageRoom, IRoom, IMessage } from '@rocket.chat/core-typings'; import { Messages, Analytics } from '@rocket.chat/models'; -import moment from 'moment'; +import { subDays } from 'date-fns'; import { convertDateToInt, diffBetweenDaysInclusive, convertIntToDate, getTotalOfWeekItems } from './date'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; @@ -39,7 +39,7 @@ export const fillFirstDaysOfMessagesIfNeeded = async (date: Date): Promise date: convertDateToInt(date), }).toArray(); if (!messagesFromAnalytics.length) { - const startOfPeriod = moment(date).subtract(90, 'days').toDate(); + const startOfPeriod = subDays(date, 90); const messages = await Messages.getTotalOfMessagesSentByDate({ start: startOfPeriod, end: date, @@ -73,10 +73,10 @@ export const findWeeklyMessagesSentData = async ({ }; }> => { const daysBetweenDates = diffBetweenDaysInclusive(end, start); - const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); - const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); + const endOfLastWeek = subDays(start, 1); + const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); const today = convertDateToInt(end); - const yesterday = convertDateToInt(moment(end).clone().subtract(1, 'days').toDate()); + const yesterday = convertDateToInt(subDays(end, 1)); const currentPeriodMessages = await Analytics.getMessagesSentTotalByDate({ start: convertDateToInt(start), end: convertDateToInt(end), diff --git a/apps/meteor/ee/server/lib/engagementDashboard/users.ts b/apps/meteor/ee/server/lib/engagementDashboard/users.ts index bed16b60903cd..13aa7adc5bed4 100644 --- a/apps/meteor/ee/server/lib/engagementDashboard/users.ts +++ b/apps/meteor/ee/server/lib/engagementDashboard/users.ts @@ -1,6 +1,6 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Users, Analytics, Sessions } from '@rocket.chat/models'; -import moment from 'moment'; +import { isValid, subDays, subHours } from 'date-fns'; import { convertDateToInt, diffBetweenDaysInclusive, getTotalOfWeekItems, convertIntToDate } from './date'; @@ -9,6 +9,10 @@ export const handleUserCreated = async (user: IUser): Promise => { return user; } + if (!isValid(user.createdAt)) { + return user; + } + await Analytics.saveUserData({ date: convertDateToInt(user.createdAt), }); @@ -22,7 +26,7 @@ export const fillFirstDaysOfUsersIfNeeded = async (date: Date): Promise => date: convertDateToInt(date), }).toArray(); if (!usersFromAnalytics.length) { - const startOfPeriod = moment(date).subtract(90, 'days').toDate(); + const startOfPeriod = subDays(date, 90); const users = await Users.getTotalOfRegisteredUsersByDate({ start: startOfPeriod, end: date, @@ -54,10 +58,10 @@ export const findWeeklyUsersRegisteredData = async ({ }; }> => { const daysBetweenDates = diffBetweenDaysInclusive(end, start); - const endOfLastWeek = moment(start).clone().subtract(1, 'days').toDate(); - const startOfLastWeek = moment(endOfLastWeek).clone().subtract(daysBetweenDates, 'days').toDate(); + const endOfLastWeek = subDays(start, 1); + const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates); const today = convertDateToInt(end); - const yesterday = convertDateToInt(moment(end).clone().subtract(1, 'days').toDate()); + const yesterday = convertDateToInt(subDays(end, 1)); const currentPeriodUsers = await Analytics.getTotalOfRegisteredUsersByDate({ start: convertDateToInt(start), end: convertDateToInt(end), @@ -85,19 +89,12 @@ export const findWeeklyUsersRegisteredData = async ({ }; }; -const createDestructuredDate = ( - input: moment.MomentInput, -): { - year: number; - month: number; - day: number; -} => { - const date = moment(input); - +const createDestructuredDate = (input: Date | number): { year: number; month: number; day: number } => { + const date = new Date(input); return { - year: date.year(), - month: date.month() + 1, - day: date.date(), + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), }; }; @@ -133,7 +130,7 @@ export const findBusiestsChatsInADayByHours = async ({ }[]; }> => ({ hours: await Sessions.getBusiestTimeWithinHoursPeriod({ - start: moment(start).subtract(24, 'hours').toDate(), + start: subHours(start, 24), end: start, groupSize: 2, }), @@ -152,7 +149,7 @@ export const findBusiestsChatsWithinAWeek = async ({ }[]; }> => ({ month: await Sessions.getTotalOfSessionsByDayBetweenDates({ - start: createDestructuredDate(moment(start).subtract(7, 'days')), + start: createDestructuredDate(subDays(start, 7)), end: createDestructuredDate(start), }), }); diff --git a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts index 4539f5e63fa21..58d5ff85550d7 100644 --- a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts +++ b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import chaiDateTime from 'chai-datetime'; +import { addSeconds } from 'date-fns'; import { beforeEach, describe, it } from 'mocha'; -import moment from 'moment'; import proxyquire from 'proxyquire'; import sinon from 'sinon'; @@ -133,7 +133,7 @@ describe('AutoCloseOnHoldScheduler', () => { await scheduler.init(); await scheduler.scheduleRoom('roomId', 5, 'test comment'); - const myScheduleTime = moment(new Date()).add(5, 's').toDate(); + const myScheduleTime = addSeconds(new Date(), 5); expect(mockAgendaCancel.calledBefore(mockAgendaDefine)).to.be.true; expect(mockAgendaCancel.calledWith({ name: 'omnichannel_auto_close_on_hold_scheduler-roomId' })); expect(mockAgendaDefine.calledWithMatch('omnichannel_auto_close_on_hold_scheduler-roomId')); diff --git a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts index f54837299f577..ffdb115264d78 100644 --- a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts +++ b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import chaiDateTime from 'chai-datetime'; +import { addSeconds } from 'date-fns'; import { beforeEach, describe, it } from 'mocha'; -import moment from 'moment'; import proxyquire from 'proxyquire'; import sinon from 'sinon'; @@ -102,7 +102,7 @@ describe('AutoTransferChats', () => { await scheduler.init(); scheduler.unscheduleRoom = sinon.stub(); - const myScheduleTime = moment(new Date()).add(10, 's').toDate(); + const myScheduleTime = addSeconds(new Date(), 10); await scheduler.scheduleRoom('roomId', 10); expect(scheduler.unscheduleRoom.calledWith('roomId')).to.be.true; diff --git a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts index 192038229bc69..361e56c7d0b30 100644 --- a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts +++ b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; +import { addMinutes } from 'date-fns'; import { describe, it } from 'mocha'; -import moment from 'moment'; import proxyquire from 'proxyquire'; import sinon from 'sinon'; @@ -65,9 +65,9 @@ describe('hooks/afterInquiryQueued', () => { settingStub.get.returns(1); await afterInquiryQueued(inquiry); - const newQueueTime = moment(inquiry._updatedAt).add(1, 'minutes'); + const expectedTime = addMinutes(inquiry._updatedAt, 1); - expect(queueMonitorStub.scheduleInquiry.calledWith(inquiry._id, new Date(newQueueTime.format()))).to.be.true; + expect(queueMonitorStub.scheduleInquiry.calledWith(inquiry._id, expectedTime)).to.be.true; }); it('should call .scheduleInquiry with proper data when more than 1 min is passed as param', async () => { @@ -79,8 +79,8 @@ describe('hooks/afterInquiryQueued', () => { settingStub.get.returns(3); await afterInquiryQueued(inquiry); - const newQueueTime = moment(inquiry._updatedAt).add(3, 'minutes'); + const expectedTime = addMinutes(inquiry._updatedAt, 3); - expect(queueMonitorStub.scheduleInquiry.calledWith(inquiry._id, new Date(newQueueTime.format()))).to.be.true; + expect(queueMonitorStub.scheduleInquiry.calledWith(inquiry._id, expectedTime)).to.be.true; }); }); diff --git a/apps/meteor/server/lib/dataExport/processDataDownloads.ts b/apps/meteor/server/lib/dataExport/processDataDownloads.ts index 5c31fcb09c8a1..33875139711a2 100644 --- a/apps/meteor/server/lib/dataExport/processDataDownloads.ts +++ b/apps/meteor/server/lib/dataExport/processDataDownloads.ts @@ -5,7 +5,7 @@ import { access, mkdir, rm, writeFile } from 'node:fs/promises'; import type { IExportOperation, IUser, RoomType } from '@rocket.chat/core-typings'; import { Avatars, ExportOperations, UserDataFiles, Subscriptions } from '@rocket.chat/models'; import { escapeHTML } from '@rocket.chat/string-helpers'; -import moment from 'moment'; +import { differenceInDays } from 'date-fns'; import { FileUpload } from '../../../app/file-upload/server'; import { settings } from '../../../app/settings/server'; @@ -206,7 +206,7 @@ const continueExportOperation = async function (exportOperation: IExportOperatio const zipFolder = settings.get('UserData_FileSystemZipPath')?.trim() || '/tmp/zipFiles'; if (exportOperation.status === 'downloading') { - for await (const attachmentData of exportOperation.fileList) { + for (const attachmentData of exportOperation.fileList) { await copyFileUpload(attachmentData, exportOperation.assetsPath); } @@ -259,7 +259,7 @@ export async function processDataDownloads(): Promise { if (operation.status !== 'pending') { // If the operation has started but was not updated in over a day, then skip it - if (operation._updatedAt && moment().diff(moment(operation._updatedAt), 'days') > 1) { + if (operation._updatedAt && differenceInDays(new Date(), operation._updatedAt) > 1) { operation.status = 'skipped'; await ExportOperations.updateOperation(operation); return processDataDownloads(); diff --git a/apps/meteor/server/lib/dataExport/sendViaEmail.ts b/apps/meteor/server/lib/dataExport/sendViaEmail.ts index 834128a6a7f2a..8dcdc546c4713 100644 --- a/apps/meteor/server/lib/dataExport/sendViaEmail.ts +++ b/apps/meteor/server/lib/dataExport/sendViaEmail.ts @@ -1,12 +1,10 @@ import type { IMessage, IUser } from '@rocket.chat/core-typings'; import { Messages, Users } from '@rocket.chat/models'; import { escapeHTML } from '@rocket.chat/string-helpers'; -import moment from 'moment'; import * as Mailer from '../../../app/mailer/server/api'; import { settings } from '../../../app/settings/server'; import { Message } from '../../../app/ui-utils/server'; -import { getMomentLocale } from '../getMomentLocale'; export async function sendViaEmail( data: { @@ -48,18 +46,12 @@ export async function sendViaEmail( emails.push(emailAddress); }); - const email = user.emails?.[0]?.address; const lang = data.language || user.language || 'en'; - const localMoment = moment(); - - if (lang !== 'en') { - const localeFn = await getMomentLocale(lang); - if (localeFn) { - Function(localeFn).call({ moment }); - localMoment.locale(lang); - } - } + const dateTimeFormatter = new Intl.DateTimeFormat(lang, { + dateStyle: 'short', + timeStyle: 'short', + }); const html = ( await Messages.findByRoomIdAndMessageIds(data.rid, data.messages, { @@ -67,13 +59,15 @@ export async function sendViaEmail( }).toArray() ) .map((message: IMessage) => { - const dateTime = moment(message.ts).locale(lang).format('L LT'); + const dateTime = dateTimeFormatter.format(new Date(message.ts)); return `

${escapeHTML( message.u.username ?? '', )} ${dateTime}
${Message.parse(message, data.language)}

`; }) .join(''); + const email = user.emails?.[0]?.address; + await Mailer.send({ to: emails, from: settings.get('From_Email'), diff --git a/apps/meteor/server/lib/getMomentLocale.ts b/apps/meteor/server/lib/getMomentLocale.ts deleted file mode 100644 index 2f1a8168dd5db..0000000000000 --- a/apps/meteor/server/lib/getMomentLocale.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -const mapLocaleToMomentLocale: Record = { - ug: 'ug-cn', - zh: 'zh-cn', -}; - -export async function getMomentLocale(locale: string): Promise { - const localeLower = locale.toLowerCase(); - const localesPaths = [ - `moment-locales/${localeLower}.js`, - `moment-locales/${String(localeLower.split('-').shift())}.js`, - `moment-locales/${mapLocaleToMomentLocale[localeLower]}.js`, - ]; - for await (const localePath of localesPaths) { - try { - return await Assets.getTextAsync(localePath); - } catch (error) { - continue; - } - } - throw new Meteor.Error('moment-locale-not-found', `Moment locale not found: ${locale}`); -} diff --git a/apps/meteor/server/methods/loadLocale.ts b/apps/meteor/server/methods/loadLocale.ts index 891f708037b99..75aa1592f107f 100644 --- a/apps/meteor/server/methods/loadLocale.ts +++ b/apps/meteor/server/methods/loadLocale.ts @@ -2,8 +2,6 @@ import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { getMomentLocale } from '../lib/getMomentLocale'; - declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -11,14 +9,11 @@ declare module '@rocket.chat/ddp-client' { } } +// Locale loading for date formatting is now handled client-side with date-fns/Intl. +// This method is kept for backwards compatibility but returns undefined. Meteor.methods({ loadLocale(locale) { check(locale, String); - - try { - return getMomentLocale(locale); - } catch (error: any) { - throw new Meteor.Error(error.message, `Moment locale not found: ${locale}`); - } + return undefined; }, }); diff --git a/apps/meteor/server/services/nps/notification.ts b/apps/meteor/server/services/nps/notification.ts index e8e7e621d2c7e..9345357774934 100644 --- a/apps/meteor/server/services/nps/notification.ts +++ b/apps/meteor/server/services/nps/notification.ts @@ -1,7 +1,7 @@ import type { IBanner } from '@rocket.chat/core-typings'; import { BannerPlatform } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; -import moment from 'moment'; +import { format } from 'date-fns'; import { settings } from '../../../app/settings/server'; import { i18n } from '../../lib/i18n'; @@ -32,7 +32,7 @@ export const getBannerForAdmins = (expireAt: Date): Omit => { text: { type: 'plain_text', text: i18n.t('NPS_survey_is_scheduled_to-run-at__date__for_all_users', { - date: moment(expireAt).format('YYYY-MM-DD'), + date: format(expireAt, 'yyyy-MM-dd'), lng, }), emoji: false, @@ -47,7 +47,7 @@ export const notifyAdmins = (expireAt: Date) => sendMessagesToAdmins({ msgs: async ({ adminUser }: { adminUser: any }): Promise => ({ msg: i18n.t('NPS_survey_is_scheduled_to-run-at__date__for_all_users', { - date: moment(expireAt).format('YYYY-MM-DD'), + date: format(expireAt, 'yyyy-MM-dd'), lng: adminUser.language, }), }), diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 067cd9474ce7d..62ed76735998f 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -2,7 +2,7 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IOmnichannelService } from '@rocket.chat/core-services'; import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; -import moment from 'moment'; +import { format } from 'date-fns'; import { OmnichannelQueue } from './queue'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; @@ -53,7 +53,7 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha } async isWithinMACLimit(room: AtLeast): Promise { - const currentMonth = moment.utc().format('YYYY-MM'); + const currentMonth = format(new Date(), 'yyyy-MM'); return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts')); } } diff --git a/apps/meteor/server/settings/smarsh.ts b/apps/meteor/server/settings/smarsh.ts index 3e52b92edc79c..38e5a111d6828 100644 --- a/apps/meteor/server/settings/smarsh.ts +++ b/apps/meteor/server/settings/smarsh.ts @@ -1,5 +1,3 @@ -import moment from 'moment-timezone'; - import { settingsRegistry } from '../../app/settings/server'; export const smarshIntervalValuesToCronMap: Record = { @@ -34,12 +32,12 @@ export const createSmarshSettings = () => placeholder: 'no-email@example.com', }); - const zoneValues = moment.tz.names().map(function _timeZonesToSettings(name) { - return { - key: name, - i18nLabel: name, - }; - }); + const intl = Intl as typeof Intl & { supportedValuesOf?(key: 'timeZone'): string[] }; + const zoneNames = typeof intl.supportedValuesOf === 'function' ? intl.supportedValuesOf('timeZone') : []; + const zoneValues = zoneNames.map((name) => ({ + key: name, + i18nLabel: name, + })); await this.add('Smarsh_Timezone', 'America/Los_Angeles', { type: 'select', values: zoneValues, diff --git a/apps/meteor/tests/end-to-end/api/livechat/23-mac.ts b/apps/meteor/tests/end-to-end/api/livechat/23-mac.ts index 3b64f20c828d0..c76fff538a6ab 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/23-mac.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/23-mac.ts @@ -1,7 +1,6 @@ import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { before, afterEach, after, describe, it } from 'mocha'; -import moment from 'moment'; import { api, getCredentials, request, credentials } from '../../../data/api-data'; import { @@ -69,7 +68,7 @@ describe('MAC', () => { const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId }); expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(multipleContactsVisitor._id); expect(res.body.contact).to.have.property('activity').that.is.an('array').with.lengthOf(1); - expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM')); + expect(res.body.contact.activity[0]).to.equal(new Date().toISOString().slice(0, 7)); }); it('should mark multiple rooms as active when they come from same contact after an agent sends a message', async () => { @@ -91,7 +90,7 @@ describe('MAC', () => { const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId }); expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(multipleContactsVisitor._id); expect(res.body.contact).to.have.property('activity').that.is.an('array').with.lengthOf(1); - expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM')); + expect(res.body.contact.activity[0]).to.equal(new Date().toISOString().slice(0, 7)); await closeOmnichannelRoom(room._id); }); @@ -99,7 +98,7 @@ describe('MAC', () => { const room = await createLivechatRoom(multipleContactsVisitor.token); expect(room).to.have.nested.property('v.activity').and.to.be.an('array'); - expect(room.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true; + expect(room.v.activity?.includes(new Date().toISOString().slice(0, 7))).to.be.true; await closeOmnichannelRoom(room._id); }); @@ -108,8 +107,8 @@ describe('MAC', () => { const inquiry = await fetchInquiry(room._id); expect(inquiry).to.have.nested.property('v.activity').and.to.be.an('array'); - expect(inquiry.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true; - expect(room.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true; + expect(inquiry.v.activity?.includes(new Date().toISOString().slice(0, 7))).to.be.true; + expect(room.v.activity?.includes(new Date().toISOString().slice(0, 7))).to.be.true; await closeOmnichannelRoom(room._id); }); @@ -127,7 +126,7 @@ describe('MAC', () => { expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(visitor._id); expect(res.body.contact).to.have.nested.property('activity').and.to.be.an('array').with.lengthOf(1); - expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM')); + expect(res.body.contact.activity[0]).to.equal(new Date().toISOString().slice(0, 7)); await closeOmnichannelRoom(room._id); }); }); diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json index bfad54193420d..dc2a0139f79fc 100644 --- a/ee/apps/omnichannel-transcript/package.json +++ b/ee/apps/omnichannel-transcript/package.json @@ -40,7 +40,6 @@ "i18next-sprintf-postprocessor": "^0.2.2", "mem": "^8.1.1", "moleculer": "^0.14.35", - "moment-timezone": "^0.5.48", "mongo-message-queue": "^1.1.0", "mongodb": "6.16.0", "nats": "^2.28.2", diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json index d1ba5b797aa56..0e24dbebb31c0 100644 --- a/ee/apps/queue-worker/package.json +++ b/ee/apps/queue-worker/package.json @@ -34,7 +34,6 @@ "eventemitter3": "^5.0.4", "mem": "^8.1.1", "moleculer": "^0.14.35", - "moment-timezone": "^0.5.48", "mongo-message-queue": "^1.1.0", "mongodb": "6.16.0", "nats": "^2.28.2", diff --git a/ee/packages/omnichannel-services/package.json b/ee/packages/omnichannel-services/package.json index ce32a970cb410..2fe891c20ee0e 100644 --- a/ee/packages/omnichannel-services/package.json +++ b/ee/packages/omnichannel-services/package.json @@ -36,7 +36,6 @@ "eventemitter3": "^5.0.4", "i18next": "~23.4.9", "mem": "^8.1.1", - "moment-timezone": "^0.5.48", "mongo-message-queue": "^1.1.0", "mongodb": "6.16.0", "pino": "10.3.1" diff --git a/ee/packages/pdf-worker/package.json b/ee/packages/pdf-worker/package.json index d3ea6ac804534..351c2bab37de8 100644 --- a/ee/packages/pdf-worker/package.json +++ b/ee/packages/pdf-worker/package.json @@ -18,12 +18,12 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@date-fns/tz": "^1.4.1", "@react-pdf/renderer": "^3.4.5", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/fuselage-tokens": "~0.33.2", + "date-fns": "~4.1.0", "emoji-toolkit": "^7.0.1", - "moment": "^2.30.1", - "moment-timezone": "^0.5.48", "react": "~18.3.1", "react-i18next": "~13.2.2" }, diff --git a/packages/agenda/package.json b/packages/agenda/package.json index c6298107be765..bf776b2bdd1cd 100644 --- a/packages/agenda/package.json +++ b/packages/agenda/package.json @@ -14,11 +14,10 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "cron": "~1.8.2", + "cron": "4.4.0", "date.js": "~0.3.3", "debug": "~4.3.7", "human-interval": "^2.0.1", - "moment-timezone": "~0.5.48", "mongodb": "6.16.0" }, "devDependencies": { diff --git a/packages/agenda/src/Job.ts b/packages/agenda/src/Job.ts index 5d5af16b8ff77..1791acfde67c8 100644 --- a/packages/agenda/src/Job.ts +++ b/packages/agenda/src/Job.ts @@ -2,7 +2,6 @@ import { CronTime } from 'cron'; import date from 'date.js'; import debugInitializer from 'debug'; import humanInterval from 'human-interval'; -import moment from 'moment-timezone'; import type { Agenda, RepeatOptions } from './Agenda'; import type { IJob, IJobAttributes } from './definition/IJob'; @@ -15,6 +14,8 @@ export type JobArgs = { agenda: Agenda; } & IJob; +type CronNextDate = { valueOf(): number }; + export class Job { public agenda: Agenda; @@ -55,26 +56,21 @@ export class Job { return this; } - public dateForTimezone(date: Date, timezone?: string | null): moment.Moment { - const newDate = moment(date); - if (timezone) { - newDate.tz(timezone); - } - - return newDate; + public dateForTimezone(date: Date, _timezone?: string | null): Date { + return date; } private _computeFromInterval(interval: string | number, previousNextRunAt: Date): void { const { repeatTimezone: timezone, name, _id } = this.attrs; debug('[%s:%s] computing next run via interval [%s]', name, _id, interval); - const lastRun = this.dateForTimezone(this.attrs.lastRunAt || new Date(), timezone); + const lastRun = this.attrs.lastRunAt || new Date(); try { const cronTime = new CronTime(interval); - let nextDate = cronTime._getNextDateFrom(lastRun); - if (nextDate.valueOf() === lastRun.valueOf() || nextDate.valueOf() <= previousNextRunAt.valueOf()) { + let nextDate = cronTime._getNextDateFrom(lastRun, timezone ?? undefined) as CronNextDate; + if (nextDate.valueOf() === lastRun.getTime() || nextDate.valueOf() <= previousNextRunAt.getTime()) { // Handle cronTime giving back the same date for the next run time - nextDate = cronTime._getNextDateFrom(this.dateForTimezone(new Date(lastRun.valueOf() + 1000), timezone)); + nextDate = cronTime._getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined) as CronNextDate; } this.attrs.nextRunAt = new Date(nextDate.valueOf()); @@ -85,10 +81,10 @@ export class Job { const numberInterval = (typeof interval === 'number' ? interval : humanInterval(interval)) || 0; if (!this.attrs.lastRunAt && numberInterval) { - this.attrs.nextRunAt = new Date(lastRun.valueOf()); + this.attrs.nextRunAt = new Date(lastRun.getTime()); debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString()); } else { - this.attrs.nextRunAt = new Date(lastRun.valueOf() + numberInterval); + this.attrs.nextRunAt = new Date(lastRun.getTime() + numberInterval); debug('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, this.attrs.nextRunAt.toISOString()); } // Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 921b3805a316b..4defe03a43d83 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3492,7 +3492,7 @@ "Message_CustomFields_Description": "Custom Fields will be validated according to the rules defined in this setting.\nCheck [ajv.js.org](https://ajv.js.org/json-schema.html) for more information regarding validation options.\nProperties `type` and `additionalProperties` will be forced to `object` and `false` respectively.", "Message_CustomFields_Enabled": "Allow Custom Fields in Messages", "Message_DateFormat": "Date Format", - "Message_DateFormat_Description": "See also: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_DateFormat_Description": "Format tokens: L, LL, LLL, YYYY, MM, DD, etc.", "Message_Description": "Configure message settings.", "Message_ErasureType": "Message Erasure Type", "Message_ErasureType_Delete": "Delete All Messages", @@ -3569,7 +3569,7 @@ "Message_TimeAndDateFormat": "Time and Date Format", "Message_TimeAndDateFormat_Description": "See also: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", "Message_TimeFormat": "Time Format", - "Message_TimeFormat_Description": "See also: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_TimeFormat_Description": "Format tokens: LT, LTS, H, h, mm, A, etc.", "Message_UserId": "User Id", "Message_VideoRecorderEnabled": "Video Recorder Enabled", "Message_VideoRecorderEnabledDescription": "Requires 'video/webm' files to be an accepted media type within 'File Upload' settings.", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index 197837da1583e..68851501c69da 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -2453,7 +2453,7 @@ "Message_Code_highlight": "Lista de lenguajes para resaltar código", "Message_Code_highlight_Description": "Lista de lenguajes separados por comas (todos los lenguajes admitidos se pueden consultar en [highlight.js](https://github.com/highlightjs/highlight.js/tree/11.6.0#supported-languages)) que se usarán para resaltar bloques de código", "Message_DateFormat": "Formato de fecha", - "Message_DateFormat_Description": "Ver también: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_DateFormat_Description": "Tokens de formato: L, LL, LLL, YYYY, MM, DD, etc.", "Message_ErasureType": "Tipo de borrado de mensaje", "Message_ErasureType_Delete": "Eliminar todos los mensajes", "Message_ErasureType_Description": "Determinar qué hacer con los mensajes de los usuarios que eliminan su cuenta.\n - **Mantener mensajes y nombre de usuario:** el historial de mensajes y archivos del usuario se eliminará de los mensajes directos y se mantendrá en otras salas.\n- **Eliminar todos los mensajes:** todos los mensajes y archivos del usuario se eliminarán de la base de datos y el usuario ya no se podrá localizar. \n - **Eliminar enlace entre usuario y mensajes:** con esta opción se asignarán todos los mensajes y archivos del usuario al bot Rocket.Cat y se borrarán todos los mensajes directos.", @@ -2509,7 +2509,7 @@ "Message_TimeAndDateFormat": "Formato de fecha y hora", "Message_TimeAndDateFormat_Description": "Ver también: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", "Message_TimeFormat": "Formato de tiempo", - "Message_TimeFormat_Description": "Ver también: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_TimeFormat_Description": "Tokens de formato: LT, LTS, H, h, mm, A, etc.", "Message_UserId": "ID de usuario", "Message_VideoRecorderEnabled": "Habilitar grabadora de vídeo", "Message_VideoRecorderEnabledDescription": "Requiere que los archivos de \"video/webm\" sean de un tipo de medio que se acepte en la configuración de \"Subida de archivos\".", diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index 7e5e83079f01e..cc0b5f10905d3 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -2857,7 +2857,7 @@ "Message_CustomDomain_AutoLink": "ऑटो लिंक के लिए कस्टम डोमेन श्वेतसूची", "Message_CustomDomain_AutoLink_Description": "यदि आप `https://internaltool.intranet` या `internaltool.intranet` जैसे आंतरिक लिंक को ऑटो लिंक करना चाहते हैं, तो आपको फ़ील्ड में `इंट्रानेट` डोमेन जोड़ना होगा, कई डोमेन को अल्पविराम से अलग करना होगा।", "Message_DateFormat": "तारिख का प्रारूप", - "Message_DateFormat_Description": "यह भी देखें: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_DateFormat_Description": "फ़ॉर्मैट टोकन: L, LL, LLL, YYYY, MM, DD, आदि।", "Message_Description": "संदेश सेटिंग कॉन्फ़िगर करें.", "Message_ErasureType": "संदेश मिटाने का प्रकार", "Message_ErasureType_Delete": "सभी संदेश हटाएँ", @@ -2916,7 +2916,7 @@ "Message_TimeAndDateFormat": "समय और दिनांक प्रारूप", "Message_TimeAndDateFormat_Description": "यह भी देखें: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", "Message_TimeFormat": "समय स्वरूप", - "Message_TimeFormat_Description": "यह भी देखें: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_TimeFormat_Description": "फ़ॉर्मैट टोकन: LT, LTS, H, h, mm, A, आदि।", "Message_UserId": "उपयोगकर्ता पहचान", "Message_VideoRecorderEnabled": "वीडियो रिकॉर्डर सक्षम", "Message_VideoRecorderEnabledDescription": "'फ़ाइल अपलोड' सेटिंग्स के अंतर्गत 'वीडियो/वेबएम' फ़ाइलों को एक स्वीकृत मीडिया प्रकार होना आवश्यक है।", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 919afd494841b..e5c07e0d67d5f 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -3451,7 +3451,7 @@ "Message_CustomFields_Description": "Os campos personalizados serão validados de acordo com as regras definidas nessa configuração.\nConsulte [ajv.js.org](https://ajv.js.org/json-schema.html) para obter mais informações sobre as opções de validação.\nAs propriedades `type` e `additionalProperties` serão forçadas a `object` e `false`, respectivamente.", "Message_CustomFields_Enabled": "Permitir campos personalizados em mensagens", "Message_DateFormat": "Formato de data", - "Message_DateFormat_Description": "Veja também: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_DateFormat_Description": "Tokens de formato: L, LL, LLL, YYYY, MM, DD, etc.", "Message_Description": "Configurar definições de mensagens.", "Message_ErasureType": "Tipo de exclusão de mensagem", "Message_ErasureType_Delete": "Excluir todas as mensagens", @@ -3528,7 +3528,7 @@ "Message_TimeAndDateFormat": "Formato de hora e data", "Message_TimeAndDateFormat_Description": "Veja também: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", "Message_TimeFormat": "Formato de hora", - "Message_TimeFormat_Description": "Veja também: [Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_TimeFormat_Description": "Tokens de formato: LT, LTS, H, h, mm, A, etc.", "Message_UserId": "ID do usuário", "Message_VideoRecorderEnabled": "Gravação de vídeo habilitada", "Message_VideoRecorderEnabledDescription": "Requer arquivos de 'vídeo/webm' para ser um tipo de mídia aceito nas configurações de 'Upload de arquivo'.", diff --git a/packages/i18n/src/locales/zh.i18n.json b/packages/i18n/src/locales/zh.i18n.json index 3050a8ac9b76a..57ad507402685 100644 --- a/packages/i18n/src/locales/zh.i18n.json +++ b/packages/i18n/src/locales/zh.i18n.json @@ -3399,7 +3399,7 @@ "Message_CustomFields_Description": "自定义字段将按照此设置定义的规则进行校验。\n有关校验选项的更多信息,请查看 [ajv.js.org](https://ajv.js.org/json-schema.html)。\n属性 `type` 和 `additionalProperties` 将分别被强制为 `object` 和 `false`。", "Message_CustomFields_Enabled": "允许消息自定义字段", "Message_DateFormat": "日期格式", - "Message_DateFormat_Description": "参见:[Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_DateFormat_Description": "格式标记:L、LL、LLL、YYYY、MM、DD 等。", "Message_Description": "配置消息设置。", "Message_ErasureType": "消息擦除类型", "Message_ErasureType_Delete": "删除所有消息", @@ -3465,7 +3465,7 @@ "Message_TimeAndDateFormat": "时间和日期格式", "Message_TimeAndDateFormat_Description": "参见:[Moment.js](http://momentjs.com/docs/#/displaying/format/)", "Message_TimeFormat": "时间格式", - "Message_TimeFormat_Description": "参见:[Moment.js](http://momentjs.com/docs/#/displaying/format/)", + "Message_TimeFormat_Description": "格式标记:LT、LTS、H、h、mm、A 等。", "Message_UserId": "用户 ID", "Message_VideoRecorderEnabled": "录像机启用", "Message_VideoRecorderEnabledDescription": "要求 'video/webm' 文件在 '文件上传' 设置中成为可接受的媒体类型。", diff --git a/packages/message-types/package.json b/packages/message-types/package.json index 414f9ec690ce3..225ae37a393e3 100644 --- a/packages/message-types/package.json +++ b/packages/message-types/package.json @@ -19,12 +19,10 @@ "eslint": "~9.39.4", "i18next": "~23.4.9", "jest": "~30.2.0", - "moment": "^2.30.1", "typescript": "~5.9.3" }, "peerDependencies": { - "date-fns": "~4.1.0", - "moment": "^2.30.1" + "date-fns": "~4.1.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/tools/package.json b/packages/tools/package.json index b50a25cb7fd37..b35f431d4fb7c 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -17,6 +17,9 @@ "testunit": "jest", "typecheck": "tsc -p tsconfig.json --noEmit" }, + "dependencies": { + "moment-timezone": "^0.5.48" + }, "devDependencies": { "@rocket.chat/jest-presets": "workspace:~", "@rocket.chat/tsconfig": "workspace:*", diff --git a/yarn.lock b/yarn.lock index bf30cdfcb734c..2fdee846eb77d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4327,15 +4327,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/date@npm:^3.12.1": - version: 3.12.1 - resolution: "@internationalized/date@npm:3.12.1" - dependencies: - "@swc/helpers": "npm:^0.5.0" - checksum: 10/a8178a73e65cb86357008e39e589bf5899b47a4ebd6123d96b54e3b19aade31c136d8e5f9c48c4627110f26d857e15aa4be9e189e56386a4b26c616df4ea1795 - languageName: node - linkType: hard - "@internationalized/date@npm:^3.7.0": version: 3.7.0 resolution: "@internationalized/date@npm:3.7.0" @@ -4364,15 +4355,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/number@npm:^3.6.6": - version: 3.6.6 - resolution: "@internationalized/number@npm:3.6.6" - dependencies: - "@swc/helpers": "npm:^0.5.0" - checksum: 10/7a7c8290a91bae3c1b22ab006c036b50f041162a383446360d0dd8194aa491a370057df1b2aa2cdfbccefd335cf6f4679e14608f5c24031b6852375654fa59df - languageName: node - linkType: hard - "@internationalized/string@npm:^3.2.5": version: 3.2.5 resolution: "@internationalized/string@npm:3.2.5" @@ -4382,15 +4364,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/string@npm:^3.2.8": - version: 3.2.8 - resolution: "@internationalized/string@npm:3.2.8" - dependencies: - "@swc/helpers": "npm:^0.5.0" - checksum: 10/2054baf8b2d5f32c7904b5a584e724d00ae781b3efb22c113c18d6a604f700569faf006be28929032831972272693d7dd863d324550a7385068715e3a67b8a56 - languageName: node - linkType: hard - "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -7003,20 +6976,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/focus@npm:^3.0.0-nightly-fb28ab3b4-241024": - version: 3.22.0 - resolution: "@react-aria/focus@npm:3.22.0" - dependencies: - "@swc/helpers": "npm:^0.5.0" - react-aria: "npm:3.48.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6467625ad37e4dd6f16669145f19ef75a44134364bd116959369407f1b3ff309f86fc25610b4e7c3736a1a4befd178112429749fad505b944e11cec25e3847c1 - languageName: node - linkType: hard - -"@react-aria/focus@npm:^3.19.1": +"@react-aria/focus@npm:^3.0.0-nightly-fb28ab3b4-241024, @react-aria/focus@npm:^3.19.1": version: 3.19.1 resolution: "@react-aria/focus@npm:3.19.1" dependencies: @@ -7634,21 +7594,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/utils@npm:^3.0.0-nightly-fb28ab3b4-241024": - version: 3.34.0 - resolution: "@react-aria/utils@npm:3.34.0" - dependencies: - "@swc/helpers": "npm:^0.5.0" - react-aria: "npm:3.48.0" - react-stately: "npm:3.46.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/55a120c1b33510bd18154128f8e6c7ca17de38d3950e474c92d2cd154f01c2db9c3248103d6183330f8793db47a138e02fb40ce1763736aea469f3a15d673de9 - languageName: node - linkType: hard - -"@react-aria/utils@npm:^3.27.0": +"@react-aria/utils@npm:^3.0.0-nightly-fb28ab3b4-241024, @react-aria/utils@npm:^3.27.0": version: 3.27.0 resolution: "@react-aria/utils@npm:3.27.0" dependencies: @@ -8696,7 +8642,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:^3.0.0-nightly-fb28ab3b4-241024, @react-types/shared@npm:^3.34.0": +"@react-types/shared@npm:^3.0.0-nightly-fb28ab3b4-241024": version: 3.34.0 resolution: "@react-types/shared@npm:3.34.0" peerDependencies: @@ -8959,12 +8905,11 @@ __metadata: resolution: "@rocket.chat/agenda@workspace:packages/agenda" dependencies: "@types/debug": "npm:^4.1.13" - cron: "npm:~1.8.2" + cron: "npm:4.4.0" date.js: "npm:~0.3.3" debug: "npm:~4.3.7" eslint: "npm:~9.39.4" human-interval: "npm:^2.0.1" - moment-timezone: "npm:~0.5.48" mongodb: "npm:6.16.0" typescript: "npm:~5.9.3" languageName: unknown @@ -9898,11 +9843,9 @@ __metadata: eslint: "npm:~9.39.4" i18next: "npm:~23.4.9" jest: "npm:~30.2.0" - moment: "npm:^2.30.1" typescript: "npm:~5.9.3" peerDependencies: date-fns: ~4.1.0 - moment: ^2.30.1 languageName: unknown linkType: soft @@ -10476,7 +10419,6 @@ __metadata: i18next: "npm:~23.4.9" jest: "npm:~30.2.0" mem: "npm:^8.1.1" - moment-timezone: "npm:^0.5.48" mongo-message-queue: "npm:^1.1.0" mongodb: "npm:6.16.0" pino: "npm:10.3.1" @@ -10515,7 +10457,6 @@ __metadata: i18next-sprintf-postprocessor: "npm:^0.2.2" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" - moment-timezone: "npm:^0.5.48" mongo-message-queue: "npm:^1.1.0" mongodb: "npm:6.16.0" nats: "npm:^2.28.2" @@ -10579,6 +10520,7 @@ __metadata: version: 0.0.0-use.local resolution: "@rocket.chat/pdf-worker@workspace:ee/packages/pdf-worker" dependencies: + "@date-fns/tz": "npm:^1.4.1" "@react-pdf/renderer": "npm:^3.4.5" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/fuselage-tokens": "npm:~0.33.2" @@ -10596,12 +10538,11 @@ __metadata: "@types/react": "npm:~18.3.28" "@types/react-dom": "npm:~18.3.7" buffer: "npm:~6.0.3" + date-fns: "npm:~4.1.0" emoji-toolkit: "npm:^7.0.1" eslint: "npm:~9.39.4" i18next: "npm:~23.4.9" jest: "npm:~30.2.0" - moment: "npm:^2.30.1" - moment-timezone: "npm:^0.5.48" react: "npm:~18.3.1" react-dom: "npm:~18.3.1" react-i18next: "npm:~13.2.2" @@ -10720,7 +10661,6 @@ __metadata: eventemitter3: "npm:^5.0.4" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" - moment-timezone: "npm:^0.5.48" mongo-message-queue: "npm:^1.1.0" mongodb: "npm:6.16.0" nats: "npm:^2.28.2" @@ -10910,6 +10850,7 @@ __metadata: "@types/jest": "npm:~30.0.0" eslint: "npm:~9.39.4" jest: "npm:~30.2.0" + moment-timezone: "npm:^0.5.48" typescript: "npm:~5.9.3" languageName: unknown linkType: soft @@ -14692,6 +14633,13 @@ __metadata: languageName: node linkType: hard +"@types/luxon@npm:~3.7.0": + version: 3.7.1 + resolution: "@types/luxon@npm:3.7.1" + checksum: 10/c7bc164c278393ea0be938f986c74b4cddfab9013b1aff4495b016f771ded1d5b7b7b4825b2c7f0b8799edce19c5f531c28ff434ab3dedf994ac2d99a20fd4c4 + languageName: node + linkType: hard + "@types/mailparser@npm:^3.4.6": version: 3.4.6 resolution: "@types/mailparser@npm:3.4.6" @@ -16553,15 +16501,6 @@ __metadata: languageName: node linkType: hard -"aria-hidden@npm:^1.2.3": - version: 1.2.6 - resolution: "aria-hidden@npm:1.2.6" - dependencies: - tslib: "npm:^2.0.0" - checksum: 10/1914e5a36225dccdb29f0b88cc891eeca736cdc5b0c905ab1437b90b28b5286263ed3a221c75b7dc788f25b942367be0044b2ac8ccf073a72e07a50b1d964202 - languageName: node - linkType: hard - "aria-query@npm:5.3.0": version: 5.3.0 resolution: "aria-query@npm:5.3.0" @@ -19261,6 +19200,16 @@ __metadata: languageName: node linkType: hard +"cron@npm:4.4.0": + version: 4.4.0 + resolution: "cron@npm:4.4.0" + dependencies: + "@types/luxon": "npm:~3.7.0" + luxon: "npm:~3.7.0" + checksum: 10/c969fdace813cc2b5898d755eb37a8a7e4192e4b43ef7b1a96c25bf9f54d5bcab016bfe851de297b570b67b4b5a37a2b242bde4a7c839860471ca243cdac79f6 + languageName: node + linkType: hard + "cron@npm:~1.8.2": version: 1.8.2 resolution: "cron@npm:1.8.2" @@ -27692,6 +27641,13 @@ __metadata: languageName: node linkType: hard +"luxon@npm:~3.7.0": + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be + languageName: node + linkType: hard + "lz-string@npm:^1.5.0": version: 1.5.0 resolution: "lz-string@npm:1.5.0" @@ -28725,7 +28681,7 @@ __metadata: languageName: node linkType: hard -"moment-timezone@npm:^0.5.48, moment-timezone@npm:^0.5.x, moment-timezone@npm:~0.5.48": +"moment-timezone@npm:^0.5.48, moment-timezone@npm:^0.5.x": version: 0.5.48 resolution: "moment-timezone@npm:0.5.48" dependencies: @@ -32091,26 +32047,6 @@ __metadata: languageName: node linkType: hard -"react-aria@npm:3.48.0": - version: 3.48.0 - resolution: "react-aria@npm:3.48.0" - dependencies: - "@internationalized/date": "npm:^3.12.1" - "@internationalized/number": "npm:^3.6.6" - "@internationalized/string": "npm:^3.2.8" - "@react-types/shared": "npm:^3.34.0" - "@swc/helpers": "npm:^0.5.0" - aria-hidden: "npm:^1.2.3" - clsx: "npm:^2.0.0" - react-stately: "npm:3.46.0" - use-sync-external-store: "npm:^1.6.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e70ba3a21f99967daffcb7399e6c4cc33fe9ae0ba4b13216ac3fbc150f37416d882b68ecd52f3c59852b87ef61a1c4b184066083d699d5afda1ad8b38fab8b99 - languageName: node - linkType: hard - "react-aria@patch:react-aria@npm%3A3.37.0#~/.yarn/patches/react-aria-npm-3.37.0-83959bd2fa.patch": version: 3.37.0 resolution: "react-aria@patch:react-aria@npm%3A3.37.0#~/.yarn/patches/react-aria-npm-3.37.0-83959bd2fa.patch::version=3.37.0&hash=e69ffb" @@ -32413,22 +32349,6 @@ __metadata: languageName: node linkType: hard -"react-stately@npm:3.46.0": - version: 3.46.0 - resolution: "react-stately@npm:3.46.0" - dependencies: - "@internationalized/date": "npm:^3.12.1" - "@internationalized/number": "npm:^3.6.6" - "@internationalized/string": "npm:^3.2.8" - "@react-types/shared": "npm:^3.34.0" - "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.6.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/ee2d8b0633c6ba82eb159197ddaaeb0832d318c6ed1304c7e14273d0c3dc3156c48aef0c8cf4207481dbca1cf054c6726c42c089259605213d1f08f35bebf321 - languageName: node - linkType: hard - "react-stately@patch:react-stately@npm%3A3.17.0#~/.yarn/patches/react-stately-npm-3.17.0-264cc7a43c.patch": version: 3.17.0 resolution: "react-stately@patch:react-stately@npm%3A3.17.0#~/.yarn/patches/react-stately-npm-3.17.0-264cc7a43c.patch::version=3.17.0&hash=e13f63" @@ -36868,15 +36788,6 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.6.0": - version: 1.6.0 - resolution: "use-sync-external-store@npm:1.6.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e - languageName: node - linkType: hard - "utf7@npm:>=1.0.2": version: 1.0.2 resolution: "utf7@npm:1.0.2"