Skip to content

chore: migrate simple server-side moment usages to date-fns#40077

Open
ggazzo wants to merge 3 commits intodevelopfrom
chore/remove-moment-server
Open

chore: migrate simple server-side moment usages to date-fns#40077
ggazzo wants to merge 3 commits intodevelopfrom
chore/remove-moment-server

Conversation

@ggazzo
Copy link
Copy Markdown
Member

@ggazzo ggazzo commented Apr 7, 2026

Summary

Mechanical moment→date-fns replacements across server code (47 files):

  • moment()new Date()
  • .diff()differenceInMinutes() / getTime() arithmetic
  • .format()format() / Intl.DateTimeFormat
  • addSeconds, addMinutes, subDays for date math
  • Package.json dependency updates, i18n strings, test data

No complex timezone logic or business hour changes. Those are in #40082.

Split from #39180. Depends on #40076 (client changes).

Test plan

  • Lint passes
  • TypeScript passes
  • Unit tests pass
  • API tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Updated date and time handling utilities throughout the application to improve performance and reduce dependencies.
    • Modified timezone calculations in scheduling and date-range operations.
    • Streamlined timestamp formatting and comparison logic across multiple modules.
  • Chores

    • Removed timezone library dependencies from package configuration.
    • Updated runtime dependencies for date processing utilities.
  • Documentation

    • Updated help text for date and time format settings in multiple languages to clarify supported formatting options.

@ggazzo ggazzo requested review from a team as code owners April 7, 2026 19:30
@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot bot commented Apr 7, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is targeting the wrong base branch. It should target 8.5.0, but it targets 8.4.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 7, 2026

⚠️ No Changeset found

Latest commit: 7149865

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

Walkthrough

Large systematic refactoring replacing Moment.js and moment-timezone dependencies with date-fns and native JavaScript Date APIs across ~40 files, maintaining equivalent control flow and business logic while updating timestamp calculations, formatting, arithmetic, and timezone handling patterns.

Changes

Cohort / File(s) Summary
Cloud & Livechat Registration
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts, apps/meteor/ee/app/license/server/startup.ts, apps/meteor/server/services/omnichannel/service.ts
Replaced moment.utc().format('YYYY-MM') with date-fns format(new Date(), 'yyyy-MM') for MAC period calculation; formatting pattern changes from UTC to local time.
Timestamp Difference Calculations
apps/meteor/app/irc/server/irc-bridge/index.js, apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts, apps/meteor/app/lib/server/lib/processDirectEmail.ts, apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts, apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
Replaced moment(...).diff(...) computations with native Math.abs(new Date(...).getTime() - Date.now()) for staleness/age checks; threshold comparisons and control flow preserved.
Message Timestamp Validation
apps/meteor/app/lib/server/methods/sendMessage.ts, apps/meteor/app/lib/server/methods/updateMessage.ts
Updated timestamp delta calculations for sync validation and edit blocking; sendMessage.ts uses native Date math, updateMessage.ts uses date-fns differenceInMinutes.
Livechat Contact & Queue Operations
apps/meteor/app/livechat/server/hooks/markRoomResponded.ts, apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts, apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
Replaced month formatting and time arithmetic: moment().format('YYYY-MM')format(new Date(), 'yyyy-MM'), queue scheduling now uses date-fns addMinutes, timestamp equality checks use getTime() comparison.
Auto-Close Scheduler
apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
Changed scheduled execution timestamp from moment(new Date()).add(timeout, 's').toDate() to addSeconds(new Date(), timeout) from date-fns.
Livechat Helper
apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
Replaced two time-arithmetic patterns: addSeconds for abandonment calculations, addMinutes for queue inactivity timeouts; both now use date-fns.
Smarsh & Message Export
apps/meteor/app/smarsh-connector/server/functions/generateEml.ts, apps/meteor/server/lib/dataExport/sendViaEmail.ts, apps/meteor/server/services/nps/notification.ts
Updated minute-difference and timezone-aware timestamp formatting; minute diffs use differenceInMinutes, timezone formatting uses Intl.DateTimeFormat or direct date-fns format.
OmniChannel Statistics
apps/meteor/app/statistics/server/lib/statistics.ts
Updated billablePeriod formatting and date-range calculations for MAC statistics; now uses subDays(new Date(), N) instead of moment.utc().subtract(...).toDate().
Timezone Utilities
apps/meteor/app/utils/server/lib/getTimezone.ts
Removed local timezone guessing implementation and moment-timezone usage; now delegates to @rocket.chat/tools timezone utilities (guessTimezoneFromOffset, guessTimezone).
Data Export
apps/meteor/server/lib/dataExport/processDataDownloads.ts
Replaced staleness check from moment().diff(moment(...), 'days') to date-fns differenceInDays.
Engagement Dashboard Date Utilities
apps/meteor/ee/server/lib/engagementDashboard/date.ts
Refactored date conversion and manipulation utilities: convertDateToInt now validates via isValid and formats as yyyyMMdd, convertIntToDate parses numeric strings, diffBetweenDays uses differenceInDays.
Engagement Dashboard Analytics
apps/meteor/ee/server/lib/engagementDashboard/channels.ts, apps/meteor/ee/server/lib/engagementDashboard/messages.ts, apps/meteor/ee/server/lib/engagementDashboard/users.ts
Updated all date boundary calculations across analytics queries; replaced moment(...).subtract(...).toDate() patterns with date-fns subDays/subHours; users.ts includes signature change to `createDestructuredDate(input: Date
Device Management & Session Tracking
apps/meteor/ee/server/lib/deviceManagement/session.ts
Replaced session-login email date formatting from moment().format(...) to date-fns format(new Date(), dateFormat) with explicit type casting and fallback handling.
Settings & Timezone Options
apps/meteor/server/settings/smarsh.ts
Removed moment-timezone and switched timezone option generation to Intl.supportedValuesOf('timeZone') with fallback to empty array when unsupported.
Locale Resolution Removal
apps/meteor/server/lib/getMomentLocale.ts, apps/meteor/server/methods/loadLocale.ts
Deleted getMomentLocale function and its locale-asset resolution logic; loadLocale DDP method now returns undefined without server-side resolution.
Test Suite Updates
apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts, apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts, apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts, apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
Updated test expected timestamps and assertions from moment computations to date-fns functions; MAC test now uses new Date().toISOString().slice(0, 7) for period assertions.
Job Scheduler
packages/agenda/src/Job.ts
Removed moment-timezone and made dateForTimezone return input Date unchanged; updated cron interval handling to use Date#getTime() and CronTime._getNextDateFrom(...) with timezone parameter.
Dependency Updates
ee/apps/omnichannel-transcript/package.json, ee/apps/queue-worker/package.json, ee/packages/omnichannel-services/package.json, ee/packages/pdf-worker/package.json, packages/agenda/package.json, packages/tools/package.json, packages/message-types/package.json
Removed moment and moment-timezone dependencies; pdf-worker adds @date-fns/tz and date-fns; agenda updates cron from ~1.8.2 to 4.4.0.
Internationalization
packages/i18n/src/locales/en.i18n.json, packages/i18n/src/locales/es.i18n.json, packages/i18n/src/locales/hi-IN.i18n.json, packages/i18n/src/locales/pt-BR.i18n.json, packages/i18n/src/locales/zh.i18n.json
Updated Message_DateFormat_Description and Message_TimeFormat_Description translations to remove Moment.js references and list supported date/time format tokens; removed Yesterday_at key from English locale.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: migrating server-side moment.js usages to date-fns across the codebase, which is reflected in all 47 modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 71 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/meteor/app/cloud/server/functions/buildRegistrationData.ts">

<violation number="1" location="apps/meteor/app/cloud/server/functions/buildRegistrationData.ts:72">
P2: `format(new Date(), 'yyyy-MM')` uses local time, but the previous `moment.utc()` call produced a UTC month. This can shift the billing period at month boundaries depending on server timezone. Use a UTC-based formatter to preserve behavior.</violation>
</file>

<file name="apps/meteor/server/services/omnichannel/service.ts">

<violation number="1" location="apps/meteor/server/services/omnichannel/service.ts:56">
P2: `format(new Date(), 'yyyy-MM')` formats in local time, but the previous logic used UTC. This can shift the month value around UTC boundaries and cause MAC activity to be checked against the wrong month. Use a UTC-based month string to preserve behavior.</violation>
</file>

<file name="apps/meteor/app/smarsh-connector/server/functions/generateEml.ts">

<violation number="1" location="apps/meteor/app/smarsh-connector/server/functions/generateEml.ts:66">
P2: Guard `Intl.DateTimeFormat` against invalid timezone values from settings; an invalid `Smarsh_Timezone` will throw and stop export generation.</violation>
</file>

<file name="apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts">

<violation number="1" location="apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts:52">
P2: This same‑day normalization marks hours as open before the start time (e.g., Monday 09:00 vs a 18:00–20:00 range). Remove this adjustment; the same‑day case should not subtract a week.</violation>
</file>

<file name="ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts">

<violation number="1" location="ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts:51">
P2: This test was weakened to only assert that `divider` is a string, so timezone/date-format regressions can pass undetected. It should still validate the expected formatted value.</violation>
</file>

<file name="packages/tools/src/timezone.ts">

<violation number="1" location="packages/tools/src/timezone.ts:19">
P1: `GMT` (UTC) is parsed incorrectly, producing `+NaN:00` and breaking `+00:00` timezone matching.</violation>
</file>

<file name="apps/meteor/app/livechat/server/lib/sendTranscript.ts">

<violation number="1" location="apps/meteor/app/livechat/server/lib/sendTranscript.ts:153">
P1: Guard `Intl.DateTimeFormat` against invalid timezone values; an invalid custom timezone will throw `RangeError` and break transcript sending.</violation>
</file>

<file name="apps/meteor/server/services/omnichannel-analytics/utils.ts">

<violation number="1" location="apps/meteor/server/services/omnichannel-analytics/utils.ts:9">
P1: `dayIterator` re-normalizes timezone-adjusted dates with `startOfDay`, shifting daily windows away from the requested timezone boundaries.</violation>
</file>

<file name="apps/meteor/app/statistics/server/lib/statistics.ts">

<violation number="1" location="apps/meteor/app/statistics/server/lib/statistics.ts:249">
P2: Billable period switched from UTC month to local-time month, which can select the wrong period near UTC month boundaries.</violation>
</file>

<file name="apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts">

<violation number="1" location="apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts:16">
P2: Using a fixed January 2025 reference date forces standard-time offsets, so DST timezones will generate UTC cron times that are one hour off during summer. Build the reference date from the current week in the target timezone so the offset matches the active DST rules.</violation>
</file>

<file name="apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts">

<violation number="1" location="apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts:55">
P1: UTC semantics were dropped during migration, so business-hour inactivity can be calculated against the wrong day/time on non-UTC servers.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/tools/src/timezone.ts Outdated
Comment on lines +153 to +157
const datetime = new Intl.DateTimeFormat(userLanguage, {
timeZone: timezone,
dateStyle: 'long',
timeStyle: 'short',
}).format(new Date(message.ts));
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Guard Intl.DateTimeFormat against invalid timezone values; an invalid custom timezone will throw RangeError and break transcript sending.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/livechat/server/lib/sendTranscript.ts, line 153:

<comment>Guard `Intl.DateTimeFormat` against invalid timezone values; an invalid custom timezone will throw `RangeError` and break transcript sending.</comment>

<file context>
@@ -151,7 +150,11 @@ export async function sendTranscript({
 		}
 
-		const datetime = moment.tz(message.ts, timezone).locale(userLanguage).format('LLL');
+		const datetime = new Intl.DateTimeFormat(userLanguage, {
+			timeZone: timezone,
+			dateStyle: 'long',
</file context>
Suggested change
const datetime = new Intl.DateTimeFormat(userLanguage, {
timeZone: timezone,
dateStyle: 'long',
timeStyle: 'short',
}).format(new Date(message.ts));
const date = new Date(message.ts);
const datetime = (() => {
try {
return new Intl.DateTimeFormat(userLanguage, {
timeZone: timezone,
dateStyle: 'long',
timeStyle: 'short',
}).format(date);
} catch {
return new Intl.DateTimeFormat(userLanguage, {
dateStyle: 'long',
timeStyle: 'short',
}).format(date);
}
})();
Fix with Cubic

yield m;
m.add(1, 'days');
export async function* dayIterator(from: Date, to: Date): AsyncGenerator<Date> {
let current = startOfDay(from);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: dayIterator re-normalizes timezone-adjusted dates with startOfDay, shifting daily windows away from the requested timezone boundaries.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/server/services/omnichannel-analytics/utils.ts, line 9:

<comment>`dayIterator` re-normalizes timezone-adjusted dates with `startOfDay`, shifting daily windows away from the requested timezone boundaries.</comment>

<file context>
@@ -1,30 +1,64 @@
-		yield m;
-		m.add(1, 'days');
+export async function* dayIterator(from: Date, to: Date): AsyncGenerator<Date> {
+	let current = startOfDay(from);
+	const toStart = startOfDay(to);
+	while (differenceInDays(toStart, current) >= 0) {
</file context>
Fix with Cubic

Comment thread apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts Outdated

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'));
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: format(new Date(), 'yyyy-MM') uses local time, but the previous moment.utc() call produced a UTC month. This can shift the billing period at month boundaries depending on server timezone. Use a UTC-based formatter to preserve behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/cloud/server/functions/buildRegistrationData.ts, line 72:

<comment>`format(new Date(), 'yyyy-MM')` uses local time, but the previous `moment.utc()` call produced a UTC month. This can shift the billing period at month boundaries depending on server timezone. Use a UTC-based formatter to preserve behavior.</comment>

<file context>
@@ -69,7 +69,7 @@ export async function buildWorkspaceRegistrationData<T extends string | undefine
 
 	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<string>('Enterprise_License');
</file context>
Fix with Cubic

Comment on lines +66 to 81
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);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard Intl.DateTimeFormat against invalid timezone values from settings; an invalid Smarsh_Timezone will throw and stop export generation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/smarsh-connector/server/functions/generateEml.ts, line 66:

<comment>Guard `Intl.DateTimeFormat` against invalid timezone values from settings; an invalid `Smarsh_Timezone` will throw and stop export generation.</comment>

<file context>
@@ -62,7 +62,22 @@ export const generateEml = async (): Promise<void> => {
 				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',
</file context>
Suggested change
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);
(() => {
try {
return 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, ' ');
} catch {
return new Intl.DateTimeFormat('en-US', {
timeZone: 'UTC',
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, ' ');
}
})(),
Fix with Cubic

if (currentDay === localTimeStartDay && localTimeFinish.diff(currentTime, 'days') < 0) {
localTimeFinish.add(1, 'week');
// Same-day range: normalize for "current day is start day but start time is after current" (use previous week's start)
if (currentDay === hour.start.cron.dayOfWeek && startMinutes > currentMinutes) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This same‑day normalization marks hours as open before the start time (e.g., Monday 09:00 vs a 18:00–20:00 range). Remove this adjustment; the same‑day case should not subtract a week.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts, line 52:

<comment>This same‑day normalization marks hours as open before the start time (e.g., Monday 09:00 vs a 18:00–20:00 range). Remove this adjustment; the same‑day case should not subtract a week.</comment>

<file context>
@@ -13,36 +40,20 @@ export const filterBusinessHoursThatMustBeOpened = async (
-						if (currentDay === localTimeStartDay && localTimeFinish.diff(currentTime, 'days') < 0) {
-							localTimeFinish.add(1, 'week');
+						// Same-day range: normalize for "current day is start day but start time is after current" (use previous week's start)
+						if (currentDay === hour.start.cron.dayOfWeek && startMinutes > currentMinutes) {
+							startMinutes -= MINUTES_PER_WEEK;
 						}
</file context>
Fix with Cubic

moment(newDayData.messages[1].ts).tz(newDayData.timezone).format(newDayData.dateFormat),
);
expect(result.messages[1]).toHaveProperty('divider');
expect(typeof result.messages[1].divider).toBe('string');
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test was weakened to only assert that divider is a string, so timezone/date-format regressions can pass undetected. It should still validate the expected formatted value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts, line 51:

<comment>This test was weakened to only assert that `divider` is a string, so timezone/date-format regressions can pass undetected. It should still validate the expected formatted value.</comment>

<file context>
@@ -48,10 +47,8 @@ describe('Strategies/ChatTranscript', () => {
-			moment(newDayData.messages[1].ts).tz(newDayData.timezone).format(newDayData.dateFormat),
-		);
+		expect(result.messages[1]).toHaveProperty('divider');
+		expect(typeof result.messages[1].divider).toBe('string');
 	});
 
</file context>
Fix with Cubic


const defaultValue = { contactsCount: 0, conversationsCount: 0, sources: [] };
const billablePeriod = moment.utc().format('YYYY-MM');
const billablePeriod = format(new Date(), 'yyyy-MM');
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Billable period switched from UTC month to local-time month, which can select the wrong period near UTC month boundaries.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/statistics/server/lib/statistics.ts, line 249:

<comment>Billable period switched from UTC month to local-time month, which can select the wrong period near UTC month boundaries.</comment>

<file context>
@@ -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]) => {
</file context>
Suggested change
const billablePeriod = format(new Date(), 'yyyy-MM');
const billablePeriod = new Date().toISOString().slice(0, 7);
Fix with Cubic


export function parseDayTimeInZone(day: string, timeStr: string, timezone: string): Date {
const [h = 0, m = 0] = timeStr.split(':').map(Number);
const dayIndex = DAY_NAMES.indexOf(day);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using a fixed January 2025 reference date forces standard-time offsets, so DST timezones will generate UTC cron times that are one hour off during summer. Build the reference date from the current week in the target timezone so the offset matches the active DST rules.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts, line 16:

<comment>Using a fixed January 2025 reference date forces standard-time offsets, so DST timezones will generate UTC cron times that are one hour off during summer. Build the reference date from the current week in the target timezone so the offset matches the active DST rules.</comment>

<file context>
@@ -1,13 +1,30 @@
+
+export function parseDayTimeInZone(day: string, timeStr: string, timezone: string): Date {
+	const [h = 0, m = 0] = timeStr.split(':').map(Number);
+	const dayIndex = DAY_NAMES.indexOf(day);
+	const refDate = 5 + dayIndex; // 2025-01-05 is Sunday
+	const tzDate = new TZDate(2025, 0, refDate, h, m, 0, timezone);
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/meteor/server/settings/smarsh.ts (1)

36-44: ⚠️ Potential issue | 🟡 Minor

Consider the Node 22.16.0 minimum version before including an unreachable empty fallback.

Line 36 uses an empty [] fallback for Intl.supportedValuesOf('timeZone'), which would produce an unusable select dropdown. However, with Node 22.16.0 required (declared in package.json), Intl.supportedValuesOf is guaranteed to exist since Node 12+. This makes the fallback unreachable.

If Node 22+ is a hard requirement, remove the fallback or update the ternary to assume availability. If older runtimes must be supported, import guessTimezone from @rocket.chat/tools as a safer fallback:

-		const zoneNames = typeof intl.supportedValuesOf === 'function' ? intl.supportedValuesOf('timeZone') : [];
+		const zoneNames = typeof intl.supportedValuesOf === 'function' ? intl.supportedValuesOf('timeZone') : [guessTimezone()];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/settings/smarsh.ts` around lines 36 - 44, The ternary
fallback producing an empty array for zoneNames is unnecessary given Node
>=22.16.0; replace the conditional with a direct call to
intl.supportedValuesOf('timeZone') (use const zoneNames =
intl.supportedValuesOf('timeZone')), build zoneValues from that, and keep the
add('Smarsh_Timezone', 'America/Los_Angeles', ...) call as-is; if you still need
backward compatibility for older runtimes instead, import guessTimezone from
'@rocket.chat/tools' and use it to derive a sensible default/value list rather
than returning an empty [].
packages/tools/src/timezone.ts (1)

1-7: ⚠️ Potential issue | 🟡 Minor

padOffset doesn't handle fractional timezone offsets.

The function assumes integer hour offsets, but some timezones have fractional offsets (India +5:30, Nepal +5:45, Iran +3:30). When utcOffset comes from the client (moment().utcOffset() / 60) or Slack importers (tz_offset / 3600), fractional values like 5.5 are possible. The function would produce +05.5:00 instead of +05:30, breaking timezone matching for affected users.

🐛 Proposed fix to handle fractional offsets
 const padOffset = (offset: string | number): string => {
 	const numberOffset = Number(offset);
 	const absOffset = Math.abs(numberOffset);
 	const isNegative = !(numberOffset === absOffset);
+	const hours = Math.floor(absOffset);
+	const minutes = Math.round((absOffset - hours) * 60);
 
-	return `${isNegative ? '-' : '+'}${absOffset < 10 ? `0${absOffset}` : absOffset}:00`;
+	return `${isNegative ? '-' : '+'}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/src/timezone.ts` around lines 1 - 7, padOffset currently
treats offset as integer hours and formats "+HH:00", which breaks for fractional
offsets (e.g. +5.5 => +05.30). Update padOffset to parse the input (offset:
string|number), determine sign, take absolute value, compute hours =
Math.floor(abs) and minutes = Math.round((abs - hours) * 60), zero-pad both
parts, and return `${sign}${HH}:${MM}`; ensure minutes are two digits and handle
cases where rounding yields 60 by incrementing hours accordingly. Target the
padOffset function to implement these numeric-to-HH:MM formatting changes.
apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts (1)

34-56: ⚠️ Potential issue | 🔴 Critical

This normalization makes start-day hours open from midnight.

For a normal Monday 08:00-20:00 slot, current Monday 07:00 makes Line 53 move startMinutes into the previous week, so Line 56 returns true and the business hour is considered open early. The same adjustment also misclassifies cross-day windows.

🕒 Suggested fix
-	const { day: currentDay, minutes: currentMinutes } = getCurrentDayAndTime();
+	const { minutes: currentMinutes } = getCurrentDayAndTime();
@@
-						let startMinutes = toMinutesSinceSunday(hour.start.cron.dayOfWeek, `${hour.start.cron.time}:00`);
-						const finishMinutes = toMinutesSinceSunday(hour.finish.cron.dayOfWeek, `${hour.finish.cron.time}:00`);
-
-						// Overnight range (e.g. Saturday 22:00 to Sunday 02:00)
-						if (startMinutes > finishMinutes) {
-							return currentMinutes >= startMinutes || currentMinutes < finishMinutes;
-						}
-
-						// Same-day range: normalize for "current day is start day but start time is after current" (use previous week's start)
-						if (currentDay === hour.start.cron.dayOfWeek && startMinutes > currentMinutes) {
-							startMinutes -= MINUTES_PER_WEEK;
-						}
-
-						return currentMinutes >= startMinutes && currentMinutes < finishMinutes;
+						const startMinutes = toMinutesSinceSunday(hour.start.cron.dayOfWeek, `${hour.start.cron.time}:00`);
+						let finishMinutes = toMinutesSinceSunday(hour.finish.cron.dayOfWeek, `${hour.finish.cron.time}:00`);
+						let current = currentMinutes;
+
+						if (finishMinutes <= startMinutes) {
+							finishMinutes += MINUTES_PER_WEEK;
+							if (current < startMinutes) {
+								current += MINUTES_PER_WEEK;
+							}
+						}
+
+						return current >= startMinutes && current < finishMinutes;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts`
around lines 34 - 56, The normalization that subtracts MINUTES_PER_WEEK when
currentDay === hour.start.cron.dayOfWeek and startMinutes > currentMinutes is
incorrect and causes same-day windows (e.g., Monday 08:00-20:00 at 07:00) to be
treated as already open; remove that normalization block and rely only on the
overnight check (startMinutes > finishMinutes) plus the final comparison
currentMinutes >= startMinutes && currentMinutes < finishMinutes to determine
openness. Locate the logic in filterBusinessHoursThatMustBeOpened (look for
startMinutes, finishMinutes and MINUTES_PER_WEEK) and delete the conditional
that mutates startMinutes so same-day ranges are not shifted into the previous
week.
apps/meteor/server/services/omnichannel-analytics/OverviewData.ts (1)

85-99: ⚠️ Potential issue | 🟠 Major

Use actual calendar days in the timezone, not elapsed 24-hour periods.

from and to are UTC Date objects representing the selected timezone's day boundaries. differenceInDays(to, from) + 1 counts elapsed 24-hour periods in UTC, which overshoots when the timezone crosses a DST transition. In America/New_York on November 1–2, 2025, the local range spans 2 calendar dates but 54+ hours UTC, yielding 3 instead of 2, causing Conversations_per_day to be ~33% too small. Count actual distinct calendar dates (e.g., analyticsMap.size or recompute from timezone-aware boundaries).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts` around
lines 85 - 99, The current Conversations function computes days with
differenceInDays(to, from) + 1 which measures elapsed UTC 24-hour periods and
breaks on DST; replace that with a timezone-aware count of distinct calendar
dates between from and to in the provided timezone by either using the computed
analyticsMap.size after you populate per-day keys or explicitly recomputing day
boundaries in the given timezone (e.g., convert from and to to the timezone with
utcToZonedTime and iterate calendar days or use startOfDay/eachDayOfInterval in
that timezone) so that the days variable reflects actual local calendar days
(refer to Conversations, the days variable, and analyticsMap when implementing
the change).
🧹 Nitpick comments (11)
apps/meteor/ee/server/lib/engagementDashboard/date.ts (1)

26-26: Consider validating the parsed result for consistency with convertDateToInt.

convertDateToInt now validates its input, but convertIntToDate returns the parse result without checking validity. If a malformed integer (e.g., 0 or negative) is passed, this returns an Invalid Date silently.

🔧 Optional: Add validation to convertIntToDate
-export const convertIntToDate = (intValue: number): Date => parse(String(intValue), 'yyyyMMdd', new Date());
+export const convertIntToDate = (intValue: number): Date => {
+	const date = parse(String(intValue), 'yyyyMMdd', new Date());
+	if (!isValid(date)) {
+		throw new Error(`convertIntToDate: invalid integer received: ${intValue}`);
+	}
+	return date;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts` at line 26,
convertIntToDate currently returns parse(...) without validating the result;
update convertIntToDate to reject malformed integers by first handling
non-positive inputs and then validating the parsed Date by ensuring it's valid
and round-trips with convertDateToInt (e.g., convertDateToInt(parsedDate) ===
intValue); if validation fails, throw a clear error or return a controlled
failure instead of returning an Invalid Date. Use the existing parse call and
the convertDateToInt helper to perform the consistency check.
apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts (2)

22-28: Redundant validation in checkDates.

The isValid checks at lines 23 and 26 are redundant because parseDate already validates and throws if the date is invalid. By the time checkDates is called, both start and end are guaranteed to be valid Date objects.

♻️ Suggested simplification
-const checkDates = (start: Date, end: Date) => {
-	if (!isValid(start)) {
-		throw new Error('The "start" query parameter must be a valid date.');
-	}
-	if (!isValid(end)) {
-		throw new Error('The "end" query parameter must be a valid date.');
-	}
+const checkDates = (start: Date, end: Date) => {
 	if (differenceInYears(startOfDay(end), startOfDay(start)) > 1.01) {
 		throw new Error('The "start" and "end" query parameters must be less than 1 year apart.');
 	}
 	if (isAfter(start, end)) {
 		throw new Error('The "start" query parameter must be before the "end" query parameter.');
 	}
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts` around lines 22
- 28, The checkDates function contains redundant isValid(start)/isValid(end)
checks because parseDate already validates and throws for invalid inputs; remove
the isValid checks and their Error throws inside checkDates so it simply
compares start and end (e.g., ensuring start <= end) and relies on parseDate for
validity. Update the function named checkDates to drop the isValid validations
and keep only the necessary logical comparison or range checks, referencing
parseDate as the source of input validation.

29-31: Use differenceInDays with an explicit threshold for clearer semantics.

differenceInYears returns only integer values (full years), so comparing to 1.01 is equivalent to > 1. Checking differenceInDays(startOfDay(end), startOfDay(start)) > 366 would more explicitly communicate the 1-year boundary logic and eliminate the confusing decimal threshold.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts` around lines 29
- 31, Replace the integer-year comparison in the reports validation with an
explicit day-based check: in the block using differenceInYears(startOfDay(end),
startOfDay(start)) > 1.01 (inside the reports API validation in this file), use
differenceInDays(startOfDay(end), startOfDay(start)) > 366 so the intent of a
"less than 1 year apart" check is clear and accounts for leap years; update the
thrown Error message if necessary to reflect the day-based boundary.
apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts (1)

68-81: Minor redundancy: startDate created but session.lastStartedAt re-wrapped.

Line 71 creates startDate from session.lastStartedAt, but line 74 creates another new Date(session.lastStartedAt). Consider reusing startDate for consistency.

♻️ Suggested fix
 		const startDate = new Date(session.lastStartedAt);
 		const stoppedAt = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 23, 59, 59);
 		const data = { ...formatDate(startDate), agentId: session.agentId };
-		const availableTime = differenceInSeconds(stoppedAt, new Date(session.lastStartedAt));
+		const availableTime = differenceInSeconds(stoppedAt, startDate);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts`
around lines 68 - 81, Replace the redundant new Date(session.lastStartedAt) with
the already-created startDate to avoid double-wrapping; specifically, use
startDate when computing stoppedAt and availableTime (so stoppedAt is built from
startDate's year/month/date and availableTime = differenceInSeconds(stoppedAt,
startDate)), and keep the existing calls to
LivechatAgentActivity.updateLastStoppedAt,
LivechatAgentActivity.updateServiceHistory and this._createOrUpdateSession
unchanged except for passing startDate where the extra new Date was used.
ee/packages/pdf-worker/package.json (1)

21-25: Standardize version constraints for date-fns dependencies.

Version constraint patterns differ: @date-fns/tz uses caret (^1.4.1), allowing minor/patch updates, while date-fns uses tilde (~4.1.0), allowing patch-only updates. Align these for consistency.

Since @date-fns/tz introduces timezone-aware date handling, ensure any timezone-sensitive tests include explicit UTC timezone enforcement to prevent snapshot or formatting failures across different environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/pdf-worker/package.json` around lines 21 - 25, Update package
dependency constraints to be consistent by aligning "@date-fns/tz" and
"date-fns" to the same version constraint pattern (choose either caret or tilde)
in package.json—update the entries for "@date-fns/tz" and "date-fns"
accordingly—and run install to regenerate lockfile; additionally, in any
timezone-sensitive tests that use `@date-fns/tz` or date-fns (look for test files
referencing timezone handling or functions from `@date-fns/tz`), explicitly set or
mock the timezone to UTC (e.g., set process.env.TZ or use a timezone-mocking
helper) so snapshots and formatted outputs remain deterministic across
environments.
apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts (2)

26-30: Reject malformed duration strings instead of coercing them.

durationAsSeconds() now accepts bare numbers and silently turns bad segments into 0, which makes these tests less useful for catching response-format regressions. Prefer failing fast on anything outside the expected H:mm:ss / HH:mm:ss shape.

♻️ Suggested change
-/** Parse HH:mm:ss or H:mm:ss to total seconds */
 function durationAsSeconds(value: string): number {
-	const parts = String(value).trim().split(':').map(Number);
-	const [h = 0, m = 0, s = 0] = parts.length === 3 ? parts : [0, 0, parts[0]];
-	return (h || 0) * 3600 + (m || 0) * 60 + (s || 0);
+	const match = /^(\d+):([0-5]\d):([0-5]\d(?:\.\d+)?)$/.exec(String(value).trim());
+	if (!match) {
+		throw new Error(`Unexpected duration format: ${value}`);
+	}
+
+	const [, hours, minutes, seconds] = match;
+	return Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts` around lines 26 -
30, The durationAsSeconds function currently coerces malformed inputs; instead
validate the input shape and fail fast: in durationAsSeconds(value) first trim
and assert the string matches the exact H:mm:ss or HH:mm:ss pattern (e.g.
/^\d{1,2}:\d{2}:\d{2}$/), and if it does not match throw an Error (or return a
clear failure) so bad segments aren’t silently treated as zero; once validated,
split and convert to numbers and compute seconds as before.

734-735: Pin these date-only query windows to UTC.

format() and startOfDay() are local-time based, so this yyyy-MM-dd pattern stays host-timezone dependent wherever it appears in the file. Either derive these strings in UTC or ensure this suite always runs with TZ=UTC, otherwise contributors outside UTC can hit a different day window.

Based on learnings, tests in this repo are expected to run in UTC, but UTC is not yet explicitly enforced in the shared test config/scripts.

Also applies to: 800-801, 1003-1003, 1386-1387

apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts (1)

43-43: Two different patterns for UTC weekday in the same file.

Line 43 uses toLocaleString('en-US', { timeZone: 'UTC', weekday: 'long' }) while lines 247-248 use a DAY_NAMES array with getUTCDay(). Both are correct, but using a consistent approach would improve maintainability.

♻️ Extract shared helper for consistency
const getUTCWeekdayName = (): string => {
	const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
	return DAY_NAMES[new Date().getUTCDay()];
};

Then use getUTCWeekdayName() in both onStartBusinessHours and applyAnyOpenBusinessHourToAgent.

Also applies to: 247-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts` at
line 43, The file uses two different patterns to get the UTC weekday string;
unify them by adding a shared helper (e.g., getUTCWeekdayName) and replace both
usages: the toLocaleString call in onStartBusinessHours and the DAY_NAMES[new
Date().getUTCDay()] usage in applyAnyOpenBusinessHourToAgent (and any other
occurrences) so both functions call getUTCWeekdayName() for consistency and
maintainability.
apps/meteor/app/smarsh-connector/server/functions/generateEml.ts (1)

65-80: Intl.DateTimeFormat output format may vary across environments.

The string replacements assume a specific output format from Intl.DateTimeFormat. While 'en-US' locale with explicit options should be consistent, the exact output structure (e.g., separator positions) can vary slightly across JavaScript engines.

Consider using formatToParts() for more predictable output construction if format consistency is critical for Smarsh compliance.

♻️ Alternative using formatToParts for explicit control
-				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, ' '),
-				);
+				const formatter = 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,
+				});
+				const parts = formatter.formatToParts(new Date(message.ts));
+				const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '';
+				rows.push(
+					`${get('month')}-${get('day')}-${get('year')} ${get('hour')}:${get('minute')}:${get('second')} ${get('timeZoneName')}`,
+				);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/smarsh-connector/server/functions/generateEml.ts` around
lines 65 - 80, The DateTime formatting in generateEml.ts (the
Intl.DateTimeFormat call that pushes into rows using message.ts and timeZone)
relies on string replacements which can vary by engine; replace that chain with
a deterministic assembly using Intl.DateTimeFormat.prototype.formatToParts: call
formatToParts(new Date(message.ts)) with the same options (timeZone, year,
month, day, hour, minute, second, timeZoneName, hour12:false), extract the
numeric parts and timeZoneName, pad as needed, and compose the final timestamp
string with fixed separators (e.g., YYYY-MM-DD HH:MM:SS TZ) before pushing into
rows to ensure consistent output across environments.
apps/meteor/app/livechat/server/business-hour/Helper.ts (1)

14-14: Stale comment references removed moment API.

The comment // Format: moment.format('dddd') is now misleading since Moment has been removed. Consider updating or removing it.

📝 Suggested update
 	businessHours: ILivechatBusinessHour[],
-	day: string, // Format: moment.format('dddd')
+	day: string, // Full weekday name in English (e.g., "Monday")
 ): Promise<Pick<ILivechatBusinessHour, '_id' | 'type'>[]> => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/business-hour/Helper.ts` at line 14, Update
the stale inline comment on the day parameter in Helper.ts: remove or replace
"// Format: moment.format('dddd')" with a current, accurate format description
(for example "// Format: full weekday name, e.g., 'Monday'") next to the day
parameter in the function or type that defines day so callers (and functions
like the ones in Helper.ts) know the expected string format without referencing
Moment.
apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts (1)

73-77: Reset the stub behavior here, not just the history.

saveBusinessHourStub.onSecondCall().rejects(error) persists across tests, so Line 76 leaves order-dependent behavior behind. Add resetBehavior() in this hook to keep the suite isolated.

♻️ Suggested fix
 		afterEach(() => {
 			sinon.restore();
 			loggerStub.resetHistory();
+			saveBusinessHourStub.resetBehavior();
 			saveBusinessHourStub.resetHistory();
 		});

Also applies to: 160-161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts`
around lines 73 - 77, The afterEach hook currently restores sinon and resets
histories but doesn't reset stub behavior, causing persistent configured
responses (e.g., saveBusinessHourStub.onSecondCall().rejects(error)) to leak
between tests; update the afterEach to call saveBusinessHourStub.resetBehavior()
(and any other stubs like loggerStub if they have configured behaviors) in
addition to resetHistory() so each test starts with fresh stub behavior, and
apply the same change to the other afterEach at lines referenced (around the
second occurrence).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/app/cloud/server/functions/buildRegistrationData.ts`:
- Line 72: The billing-period string is being generated with format(new Date(),
'yyyy-MM') which depends on the host timezone; update the call where MAC is
assigned (LivechatContacts.countContactsOnPeriod(...)) to use UTC-safe
formatting: replace format(new Date(), 'yyyy-MM') with new
Date().toISOString().slice(0, 7), and remove the now-unused format import from
the top of the module; also scan the other occurrences mentioned (e.g., in
license/startup, omnichannel/service, statistics, markRoomResponded) and apply
the same change so monthly reporting stays UTC-consistent.

In `@apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts`:
- Around line 81-97: The logic adding inactivity via calls to
differenceInSeconds can produce negative values when the inactivity window lies
entirely outside office hours; clamp startOfInactivity and endOfConversation to
the office-hours window (todayStartOfficeHours..todayEndOfficeHours) before
computing durations and only add to totalSeconds if the clamped interval has a
positive length. Update the branches that use differenceInSeconds (the checks
around isBefore/isAfter for startOfInactivity, endOfConversation,
todayStartOfficeHours, todayEndOfficeHours) to compute clampedStart =
max(startOfInactivity, todayStartOfficeHours) and clampedEnd =
min(endOfConversation, todayEndOfficeHours) and then add
differenceInSeconds(clampedEnd, clampedStart) only when clampedEnd >
clampedStart to avoid negative seconds.

In `@apps/meteor/app/statistics/server/lib/statistics.ts`:
- Around line 249-273: The billing period and rolling-window dates must be
computed in UTC: replace format(new Date(), 'yyyy-MM') with new
Date().toISOString().slice(0,7') for the billablePeriod used in
LivechatRooms.getMACStatisticsForPeriod (affecting
statistics.omnichannelContactsBySource), and replace subDays(today, N) usages
used before calling LivechatRooms.getMACStatisticsBetweenDates with UTC
millisecond arithmetic (e.g. new Date(Date.UTC(...)) or compute todayUtc = new
Date(Date.now()); then monthAgoUtc = new Date(todayUtc.getTime() - 30 *
24*60*60*1000) etc.) so window boundaries never shift by timezone/DST; also
remove now-unused imports format and subDays from the top of the file. Ensure
the promises pushed to statsPms use these UTC dates.

In `@apps/meteor/ee/app/license/server/startup.ts`:
- Line 115: The counter uses local server time because format(new Date(),
'yyyy-MM') was used; change it to produce a UTC-based YYYY-MM string instead so
month boundaries match tests — update the License.setLicenseLimitCounter
callback (the function that calls LivechatContacts.countContactsOnPeriod) to
compute the month in UTC (e.g. use a UTC formatting helper such as date-fns-tz's
formatInTimeZone(..., 'UTC', 'yyyy-MM') or derive the string from new
Date().toISOString().slice(0,7)) and pass that UTC month string into
LivechatContacts.countContactsOnPeriod.

In `@apps/meteor/ee/server/lib/deviceManagement/session.ts`:
- Line 58: Wrap the date-fns format(...) calls that use the dateFormat variable
in a try-catch and fall back to a date-fns-compatible format ('yyyy-MM-dd
HH:mm') on error; specifically, protect the format invocation that uses
dateFormat (the variable initialized from Message_TimeAndDateFormat) and the
other format call later in the file so that if an admin supplies Moment-style
tokens the code catches the thrown error, logs or records the formatting error,
and uses the fallback format instead to avoid crashing login email generation.

In `@apps/meteor/server/lib/dataExport/sendViaEmail.ts`:
- Around line 50-53: The Intl.DateTimeFormat construction using the variable
lang can throw RangeError for malformed locales; wrap the creation of
dateTimeFormatter in a try-catch inside sendViaEmail (and the other occurrence
around the line ~61) and if an exception is thrown, retry using the fallback
locale 'en' (e.g., try new Intl.DateTimeFormat(lang, ...) and on catch set
dateTimeFormatter = new Intl.DateTimeFormat('en', ...)); ensure you catch only
the construction error and preserve the original date/time options
(dateStyle/timeStyle) when creating the fallback formatter.

In `@apps/meteor/server/services/nps/notification.ts`:
- Line 35: The date formatting calls for expireAt and startAt are not validated
and can throw at runtime; update the code that builds the admin banner/message
to call isValid(expireAt) and isValid(startAt) (from date-fns) before using
format(expireAt, 'yyyy-MM-dd') / format(startAt, 'yyyy-MM-dd'), and provide a
safe fallback (e.g., null, empty string, or skip that field) when the date is
invalid; mirror the defensive pattern used by convertDateToInt and ensure these
checks occur in the same function that references expireAt and startAt so
invalid Date objects never reach format().

In `@apps/meteor/server/services/omnichannel-analytics/utils.ts`:
- Around line 8-13: dayIterator, hourIterator and weekIterator currently call
startOfDay/startOfHour/startOfWeek on plain Dates which uses the process
timezone; instead, convert inputs to the analytics timezone, perform truncation
and stepping there, and convert back to UTC for yielded bucket boundaries.
Specifically, in dayIterator/hourIterator/weekIterator: use date-fns-tz helpers
(utcToZonedTime to get a zoned Date from the input and timezone, call
startOfDay/startOfHour/startOfWeek on that zoned Date, then use zonedTimeToUtc
to convert the truncated zoned boundary back to an actual UTC Date to yield and
to compute the next boundary via addDays/addHours/addWeeks in the zoned
context). Ensure you use the passed _timezone (or analytics timezone variable)
consistently so buckets are computed end-to-end in that timezone.
- Around line 36-41: parseRangeInTimezone currently slices Y/M/D and hands them
to TZDate, which allows component overflow (e.g., 2026-02-31 -> 2026-03-03) and
later isValid() won't catch this; validate the Y, M, D components before
creating TZDate by constructing a plain Date (or using UTC) from (y, m-1, d) and
confirm its year, month, and date match the parsed components, and if they
don't, reject/throw or return an error so invalid calendar dates are not
silently normalized; update parseRangeInTimezone to perform this component-level
check and only build startTz/endTz when the components are valid.

In `@apps/meteor/server/services/omnichannel/service.ts`:
- Around line 55-57: The function isWithinMACLimit now uses local time via
format(new Date(), 'yyyy-MM') which breaks parity with earlier UTC-based
behavior; update the currentMonth computation inside isWithinMACLimit to
generate a UTC month string (e.g., using new Date().toISOString().slice(0,7) or
another UTC-based formatter) so the room.v.activity comparison remains
UTC-consistent, leaving the License.shouldPreventAction('monthlyActiveContacts')
call unchanged.

In `@apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts`:
- Around line 293-297: The test currently only checks existence of the totalizer
(avgWaitingTime) but the assertion that validates its numeric value was
commented out; restore the assertion by un-commenting and using
durationAsSeconds(avgWaitingTime.value) to compute avgWaitingTimeValue and
assert expect(avgWaitingTimeValue).to.be.closeTo(DELAY_BETWEEN_MESSAGES.max /
1000, 5); also ensure avgWaitingTime.value is present before conversion (or
assert it's defined) so the test fails if waiting-time calculation regresses;
locate the variables avgWaitingTime, durationAsSeconds, and
DELAY_BETWEEN_MESSAGES in the test to apply this change.

In `@apps/meteor/tests/end-to-end/api/livechat/23-mac.ts`:
- Line 71: Tests currently assert using UTC month string (new
Date().toISOString().slice(0, 7)) while production uses local time via
format(new Date(), 'yyyy-MM'); update the failing assertions in
apps/meteor/tests/end-to-end/api/livechat/23-mac.ts (and the other occurrences
noted: lines around 93, 101, 110-111, 129) to compute the month using the same
local-time helper as production — import format from 'date-fns' in the test file
and replace new Date().toISOString().slice(0, 7) with format(new Date(),
'yyyy-MM') so tests and production use the same timezone basis.

In
`@apps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.ts`:
- Around line 191-193: The mocha-based tests in OverviewData.tests.ts use
date-fns functions (startOfDay, endOfDay, startOfMonth, endOfMonth) which are
sensitive to the process timezone; update the test run environment to be
timezone-stable by adding TZ=UTC to the mocha scripts in package.json
(specifically the .testunit:server and .testunit:server:cov scripts) so all runs
use UTC, or alternatively refactor the date boundary construction in
OverviewData.tests.ts to build UTC-based dates (e.g., using Date.UTC or
equivalent) for the calls that create test boundaries referenced around
getAnalyticsBetweenDate / getOnHoldConversationsBetweenDate and the other
boundary blocks.

In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts`:
- Around line 50-51: Update the test in ChatTranscript.spec.ts to assert the
divider's formatted value/format rather than just its type: verify
result.messages[1].divider equals the expected timestamp string from the fixture
(or match a strict regex like /^\d{1,2}:\d{2} [AP]M · [A-Za-z]{3} \d{1,2}, \d{4}
UTC$/) to catch malformed outputs produced by formatInTimezone(), and also
ensure the package.json test script sets TZ=UTC so timezone-dependent formatting
is consistent across environments.

In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.ts`:
- Around line 10-22: The momentTokenMap is missing mappings for tokens used
elsewhere (causing unconverted tokens and format failures); update the
momentTokenMap object to include entries for 'MMM' -> 'MMM', 'H' -> 'H', 'mm' ->
'mm', and 'ss' -> 'ss' so momentFormatToDateFns() can correctly translate
formats used by formatDate() and fixtures; locate and edit the momentTokenMap
constant in ChatTranscript.ts to add these key/value pairs.
- Around line 33-43: formatInTimezone currently builds a plain Date from TZDate
components (losing milliseconds and interpreting the result in the server's
local timezone), which can produce incorrect results around DST or when the
server timezone differs from the target; instead, stop extracting components and
use date-fns-tz's timezone-aware formatter (e.g., formatInTimeZone or format
from date-fns-tz) directly with the original timestamp/new Date(ts) and the
converted format (momentFormatToDateFns(fmt)) — update the formatInTimezone
function to call that timezone-aware formatter (keeping TZDate and manual
reconstruction out) so milliseconds are preserved and DST/timezone semantics are
correct.

In `@packages/agenda/src/Job.ts`:
- Around line 17-18: The code uses the private CronTime._getNextDateFrom and a
fragile CronNextDate shim; change calls to the public
CronTime.getNextDateFrom(...) which returns a Luxon DateTime, and remove or
replace the CronNextDate type shim with the proper Luxon DateTime type (or
accept its valueOf()), then use dateTime.valueOf() where the code previously
used valueOf() on the shim. Update any occurrences in Job.ts that reference
_getNextDateFrom to call getNextDateFrom and adjust typings accordingly.

In `@packages/i18n/src/locales/pt-BR.i18n.json`:
- Line 3232: Update the locale string for Message_DateFormat_Description (and
the other affected key at the same region) to replace Moment-style tokens with
date-fns equivalents and/or show both sets; reference the i18n key
"Message_DateFormat_Description" and the related key at the other occurrence and
change the text to mention date-fns tokens (e.g., yyyy, MM, dd, HH:mm:ss, etc.),
note that a PDF worker conversion utility will map legacy Moment tokens to
date-fns when needed, and make the description concise so admins are guided to
use native date-fns tokens or understand that automatic conversion occurs.

---

Outside diff comments:
In
`@apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts`:
- Around line 34-56: The normalization that subtracts MINUTES_PER_WEEK when
currentDay === hour.start.cron.dayOfWeek and startMinutes > currentMinutes is
incorrect and causes same-day windows (e.g., Monday 08:00-20:00 at 07:00) to be
treated as already open; remove that normalization block and rely only on the
overnight check (startMinutes > finishMinutes) plus the final comparison
currentMinutes >= startMinutes && currentMinutes < finishMinutes to determine
openness. Locate the logic in filterBusinessHoursThatMustBeOpened (look for
startMinutes, finishMinutes and MINUTES_PER_WEEK) and delete the conditional
that mutates startMinutes so same-day ranges are not shifted into the previous
week.

In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts`:
- Around line 85-99: The current Conversations function computes days with
differenceInDays(to, from) + 1 which measures elapsed UTC 24-hour periods and
breaks on DST; replace that with a timezone-aware count of distinct calendar
dates between from and to in the provided timezone by either using the computed
analyticsMap.size after you populate per-day keys or explicitly recomputing day
boundaries in the given timezone (e.g., convert from and to to the timezone with
utcToZonedTime and iterate calendar days or use startOfDay/eachDayOfInterval in
that timezone) so that the days variable reflects actual local calendar days
(refer to Conversations, the days variable, and analyticsMap when implementing
the change).

In `@apps/meteor/server/settings/smarsh.ts`:
- Around line 36-44: The ternary fallback producing an empty array for zoneNames
is unnecessary given Node >=22.16.0; replace the conditional with a direct call
to intl.supportedValuesOf('timeZone') (use const zoneNames =
intl.supportedValuesOf('timeZone')), build zoneValues from that, and keep the
add('Smarsh_Timezone', 'America/Los_Angeles', ...) call as-is; if you still need
backward compatibility for older runtimes instead, import guessTimezone from
'@rocket.chat/tools' and use it to derive a sensible default/value list rather
than returning an empty [].

In `@packages/tools/src/timezone.ts`:
- Around line 1-7: padOffset currently treats offset as integer hours and
formats "+HH:00", which breaks for fractional offsets (e.g. +5.5 => +05.30).
Update padOffset to parse the input (offset: string|number), determine sign,
take absolute value, compute hours = Math.floor(abs) and minutes =
Math.round((abs - hours) * 60), zero-pad both parts, and return
`${sign}${HH}:${MM}`; ensure minutes are two digits and handle cases where
rounding yields 60 by incrementing hours accordingly. Target the padOffset
function to implement these numeric-to-HH:MM formatting changes.

---

Nitpick comments:
In `@apps/meteor/app/livechat/server/business-hour/Helper.ts`:
- Line 14: Update the stale inline comment on the day parameter in Helper.ts:
remove or replace "// Format: moment.format('dddd')" with a current, accurate
format description (for example "// Format: full weekday name, e.g., 'Monday'")
next to the day parameter in the function or type that defines day so callers
(and functions like the ones in Helper.ts) know the expected string format
without referencing Moment.

In `@apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts`:
- Around line 68-81: Replace the redundant new Date(session.lastStartedAt) with
the already-created startDate to avoid double-wrapping; specifically, use
startDate when computing stoppedAt and availableTime (so stoppedAt is built from
startDate's year/month/date and availableTime = differenceInSeconds(stoppedAt,
startDate)), and keep the existing calls to
LivechatAgentActivity.updateLastStoppedAt,
LivechatAgentActivity.updateServiceHistory and this._createOrUpdateSession
unchanged except for passing startDate where the extra new Date was used.

In `@apps/meteor/app/smarsh-connector/server/functions/generateEml.ts`:
- Around line 65-80: The DateTime formatting in generateEml.ts (the
Intl.DateTimeFormat call that pushes into rows using message.ts and timeZone)
relies on string replacements which can vary by engine; replace that chain with
a deterministic assembly using Intl.DateTimeFormat.prototype.formatToParts: call
formatToParts(new Date(message.ts)) with the same options (timeZone, year,
month, day, hour, minute, second, timeZoneName, hour12:false), extract the
numeric parts and timeZoneName, pad as needed, and compose the final timestamp
string with fixed separators (e.g., YYYY-MM-DD HH:MM:SS TZ) before pushing into
rows to ensure consistent output across environments.

In `@apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts`:
- Around line 22-28: The checkDates function contains redundant
isValid(start)/isValid(end) checks because parseDate already validates and
throws for invalid inputs; remove the isValid checks and their Error throws
inside checkDates so it simply compares start and end (e.g., ensuring start <=
end) and relies on parseDate for validity. Update the function named checkDates
to drop the isValid validations and keep only the necessary logical comparison
or range checks, referencing parseDate as the source of input validation.
- Around line 29-31: Replace the integer-year comparison in the reports
validation with an explicit day-based check: in the block using
differenceInYears(startOfDay(end), startOfDay(start)) > 1.01 (inside the reports
API validation in this file), use differenceInDays(startOfDay(end),
startOfDay(start)) > 366 so the intent of a "less than 1 year apart" check is
clear and accounts for leap years; update the thrown Error message if necessary
to reflect the day-based boundary.

In `@apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts`:
- Line 43: The file uses two different patterns to get the UTC weekday string;
unify them by adding a shared helper (e.g., getUTCWeekdayName) and replace both
usages: the toLocaleString call in onStartBusinessHours and the DAY_NAMES[new
Date().getUTCDay()] usage in applyAnyOpenBusinessHourToAgent (and any other
occurrences) so both functions call getUTCWeekdayName() for consistency and
maintainability.

In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts`:
- Line 26: convertIntToDate currently returns parse(...) without validating the
result; update convertIntToDate to reject malformed integers by first handling
non-positive inputs and then validating the parsed Date by ensuring it's valid
and round-trips with convertDateToInt (e.g., convertDateToInt(parsedDate) ===
intValue); if validation fails, throw a clear error or return a controlled
failure instead of returning an Invalid Date. Use the existing parse call and
the convertDateToInt helper to perform the consistency check.

In `@apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts`:
- Around line 26-30: The durationAsSeconds function currently coerces malformed
inputs; instead validate the input shape and fail fast: in
durationAsSeconds(value) first trim and assert the string matches the exact
H:mm:ss or HH:mm:ss pattern (e.g. /^\d{1,2}:\d{2}:\d{2}$/), and if it does not
match throw an Error (or return a clear failure) so bad segments aren’t silently
treated as zero; once validated, split and convert to numbers and compute
seconds as before.

In
`@apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts`:
- Around line 73-77: The afterEach hook currently restores sinon and resets
histories but doesn't reset stub behavior, causing persistent configured
responses (e.g., saveBusinessHourStub.onSecondCall().rejects(error)) to leak
between tests; update the afterEach to call saveBusinessHourStub.resetBehavior()
(and any other stubs like loggerStub if they have configured behaviors) in
addition to resetHistory() so each test starts with fresh stub behavior, and
apply the same change to the other afterEach at lines referenced (around the
second occurrence).

In `@ee/packages/pdf-worker/package.json`:
- Around line 21-25: Update package dependency constraints to be consistent by
aligning "@date-fns/tz" and "date-fns" to the same version constraint pattern
(choose either caret or tilde) in package.json—update the entries for
"@date-fns/tz" and "date-fns" accordingly—and run install to regenerate
lockfile; additionally, in any timezone-sensitive tests that use `@date-fns/tz` or
date-fns (look for test files referencing timezone handling or functions from
`@date-fns/tz`), explicitly set or mock the timezone to UTC (e.g., set
process.env.TZ or use a timezone-mocking helper) so snapshots and formatted
outputs remain deterministic across environments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 023d04fa-cc1a-4e10-8b20-b6eb7e1aedc5

📥 Commits

Reviewing files that changed from the base of the PR and between 2031ba8 and fcb678b.

📒 Files selected for processing (71)
  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts
  • apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts
  • apps/meteor/app/livechat/server/business-hour/Default.ts
  • apps/meteor/app/livechat/server/business-hour/Helper.ts
  • apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts
  • apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts
  • apps/meteor/app/livechat/server/lib/analytics/dashboards.ts
  • apps/meteor/app/livechat/server/lib/sendTranscript.ts
  • apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/ee/app/license/server/startup.ts
  • apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts
  • apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  • apps/meteor/ee/server/lib/deviceManagement/session.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • apps/meteor/package.json
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/server/lib/getMomentLocale.ts
  • apps/meteor/server/methods/loadLocale.ts
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/server/services/omnichannel-analytics/AgentData.ts
  • apps/meteor/server/services/omnichannel-analytics/OverviewData.ts
  • apps/meteor/server/services/omnichannel-analytics/service.ts
  • apps/meteor/server/services/omnichannel-analytics/utils.ts
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/tests/data/livechat/businessHours.ts
  • apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
  • apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
  • apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts
  • apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts
  • apps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.ts
  • ee/apps/omnichannel-transcript/package.json
  • ee/apps/queue-worker/package.json
  • ee/packages/omnichannel-services/package.json
  • ee/packages/pdf-worker/package.json
  • ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts
  • ee/packages/pdf-worker/src/strategies/ChatTranscript.ts
  • packages/agenda/package.json
  • packages/agenda/src/Job.ts
  • packages/i18n/src/locales/en.i18n.json
  • packages/i18n/src/locales/es.i18n.json
  • packages/i18n/src/locales/hi-IN.i18n.json
  • packages/i18n/src/locales/pt-BR.i18n.json
  • packages/i18n/src/locales/zh.i18n.json
  • packages/message-types/package.json
  • packages/tools/package.json
  • packages/tools/src/timezone.ts
💤 Files with no reviewable changes (5)
  • ee/packages/omnichannel-services/package.json
  • apps/meteor/server/lib/getMomentLocale.ts
  • ee/apps/omnichannel-transcript/package.json
  • ee/apps/queue-worker/package.json
  • packages/tools/package.json


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'));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file mentioned in the review
find . -type f -name "buildRegistrationData.ts" | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 131


🏁 Script executed:

# Check the structure to find the file
fd buildRegistrationData.ts

Repository: RocketChat/Rocket.Chat

Length of output: 129


🏁 Script executed:

# Read the file to see line 72 and context
cat -n apps/meteor/app/cloud/server/functions/buildRegistrationData.ts | head -80

Repository: RocketChat/Rocket.Chat

Length of output: 3542


🏁 Script executed:

# Check git history to see what the previous implementation was
git log --oneline -p apps/meteor/app/cloud/server/functions/buildRegistrationData.ts | head -150

Repository: RocketChat/Rocket.Chat

Length of output: 4300


🏁 Script executed:

# The git log shows this is a new file. Let me check if there's a previous version of this function
# Search for similar code patterns or related functions that might have used moment
git log --all --oneline --diff-filter=D -- "*buildRegistrationData*" | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Let me search for the old moment-based implementation that might have done the same thing
git log --all -p -S "moment.utc()" -- "*registration*" | head -200

Repository: RocketChat/Rocket.Chat

Length of output: 152


🏁 Script executed:

# Let me search more broadly for moment/date-fns usage patterns around LivechatContacts and MAC/billing
rg "countContactsOnPeriod" -B 3 -A 3 --type ts --type tsx

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Check date-fns documentation about timezone handling for format()
# Also look for other date-fns usage in the codebase with timezone considerations
rg "format\(new Date\(" --type ts --type tsx | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Search for countContactsOnPeriod usage without file type filter
rg "countContactsOnPeriod" -B 3 -A 3

Repository: RocketChat/Rocket.Chat

Length of output: 3152


🏁 Script executed:

# Look for other usages of format() with new Date() to understand patterns
rg "format\(new Date\(" | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 2650


🏁 Script executed:

# The commit fcb678b782 created this file. Let me check what file it replaced or what similar code existed before
git show fcb678b782 --stat

Repository: RocketChat/Rocket.Chat

Length of output: 50379


🏁 Script executed:

# Let me also check if there's a previous version of this in a different location
# Search for any history of functions that might have calculated monthly active contacts before
git log --all -p --follow -S "countContactsOnPeriod" | head -300

Repository: RocketChat/Rocket.Chat

Length of output: 111


🏁 Script executed:

# Look at the other location where countContactsOnPeriod is used with format
grep -n "countContactsOnPeriod" apps/meteor/ee/app/license/server/startup.ts -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 426


🏁 Script executed:

# Check how billing periods are handled elsewhere in the codebase
rg "billablePeriod|format\(new Date.*yyyy-MM" -B 1 -A 1

Repository: RocketChat/Rocket.Chat

Length of output: 2931


🏁 Script executed:

# Let's check what the date-fns format() function does with plain Date objects
# by looking at usage patterns and any timezone-related handling elsewhere
rg "formatUTC|utc.*format|timezone" apps/meteor/app/cloud --type js -A 2 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if there's any server timezone configuration or UTC enforcement
rg "TZ=UTC|timezone|utc" package.json -A 2 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if date-fns has a UTC-aware formatting option
# Look for any formatUTC or similar usage in the codebase
rg "date-fns" apps/meteor --type js | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Let's understand the migration commit better - check if there's documentation about this change
git log fcb678b782 --format=fuller

Repository: RocketChat/Rocket.Chat

Length of output: 680


🏁 Script executed:

# Verify date-fns behavior: check if there's documentation or other references to timezone handling
# Look for any related issues or comments
rg "date-fns.*timezone|date-fns.*UTC" -A 3 -B 3

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Verify the suggested fix works - check if toISOString() is used elsewhere for date formatting
rg "toISOString\(\).slice" --type js | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if there's a formatUTC or utcFormat function available in date-fns
rg "formatUTC|utc" node_modules/date-fns/package.json 2>/dev/null || echo "Checking date-fns for UTC support..."

Repository: RocketChat/Rocket.Chat

Length of output: 102


🏁 Script executed:

# Check if date-fns provides a utcToZonedTime or similar function
# Look at available date-fns imports in the project
rg "import.*from.*date-fns" apps/meteor/app/cloud/server/functions/buildRegistrationData.ts

Repository: RocketChat/Rocket.Chat

Length of output: 100


🏁 Script executed:

# Verify the actual behavior: does date-fns have UTC formatting?
# Check the date-fns version being used
grep "date-fns" package.json

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look for any documentation or comments about the timezone issue in the migration commit
git show fcb678b782 -- apps/meteor/app/cloud/server/functions/buildRegistrationData.ts | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 3885


🏁 Script executed:

# Quick confirmation that the pattern appears in license counter which is definitely critical
grep -n "monthlyActiveContacts\|MAC" apps/meteor/ee/app/license/server/startup.ts | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 204


Use UTC-safe month formatting for billing period.

format(new Date(), 'yyyy-MM') uses the host timezone. On non-UTC servers, this causes incorrect billing month reporting near UTC month boundaries. Since this tracks monthly active contacts for licensing, the regression from the prior UTC-backed approach must be corrected.

Use new Date().toISOString().slice(0, 7) instead. Also remove the now-unused format import from line 2.

Note: The same pattern appears in ee/app/license/server/startup.ts:115, omnichannel/service.ts, statistics.ts, and markRoomResponded.ts—all should be reviewed and updated together.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/cloud/server/functions/buildRegistrationData.ts` at line 72,
The billing-period string is being generated with format(new Date(), 'yyyy-MM')
which depends on the host timezone; update the call where MAC is assigned
(LivechatContacts.countContactsOnPeriod(...)) to use UTC-safe formatting:
replace format(new Date(), 'yyyy-MM') with new Date().toISOString().slice(0, 7),
and remove the now-unused format import from the top of the module; also scan
the other occurrences mentioned (e.g., in license/startup, omnichannel/service,
statistics, markRoomResponded) and apply the same change so monthly reporting
stays UTC-consistent.

Comment thread apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts Outdated
Comment thread apps/meteor/app/statistics/server/lib/statistics.ts
Comment thread apps/meteor/ee/app/license/server/startup.ts

const dateFormat = settings.get('Message_TimeAndDateFormat');

const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Setting definitions/usages =="
rg -n -C3 "Message_TimeAndDateFormat" apps/meteor packages

echo
echo "== Date format tokens near relevant setting/format usage =="
rg -n -C2 "Message_TimeAndDateFormat|moment\\(\\)\\.format|format\\(new Date\\(" apps/meteor packages

Repository: RocketChat/Rocket.Chat

Length of output: 50379


🏁 Script executed:

cat -n apps/meteor/ee/server/lib/deviceManagement/session.ts | sed -n '50,85p'

Repository: RocketChat/Rocket.Chat

Length of output: 1354


🏁 Script executed:

head -20 apps/meteor/ee/server/lib/deviceManagement/session.ts

Repository: RocketChat/Rocket.Chat

Length of output: 957


🏁 Script executed:

rg -n "Message_TimeAndDateFormat" apps/meteor --type ts -A 3 -B 1 | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 1480


🏁 Script executed:

cat -n apps/meteor/client/hooks/useFormatDateAndTime.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1123


Add error handling to date-fns format call for Message_TimeAndDateFormat setting.

At lines 58 and 77, Message_TimeAndDateFormat may contain Moment.js-style tokens (YYYY, DD, Do, etc.) per the setting's i18n description and default value LLL, but date-fns/format does not support these patterns and will throw. Wrapping the format call in a try-catch with a date-fns-compatible fallback ('yyyy-MM-dd HH:mm') ensures login email generation does not crash if an admin customizes the setting with unsupported tokens.

Hardening suggestion
-		const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
+		const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
+		let formattedDate: string;
+		try {
+			formattedDate = format(new Date(), dateFormat);
+		} catch {
+			formattedDate = format(new Date(), 'yyyy-MM-dd HH:mm');
+		}
@@
-			date: format(new Date(), dateFormat),
+			date: formattedDate,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/deviceManagement/session.ts` at line 58, Wrap the
date-fns format(...) calls that use the dateFormat variable in a try-catch and
fall back to a date-fns-compatible format ('yyyy-MM-dd HH:mm') on error;
specifically, protect the format invocation that uses dateFormat (the variable
initialized from Message_TimeAndDateFormat) and the other format call later in
the file so that if an admin supplies Moment-style tokens the code catches the
thrown error, logs or records the formatting error, and uses the fallback format
instead to avoid crashing login email generation.

Comment thread ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts Outdated
Comment on lines +10 to +22
const momentTokenMap: Record<string, string> = {
LT: 'p',
LTS: 'pp',
LLLL: 'EEEE, PPP p',
LLL: 'PPP p',
LL: 'PPP',
L: 'P',
YYYY: 'yyyy',
YY: 'yy',
DD: 'dd',
D: 'd',
Do: 'do',
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for dateFormat and timeAndDateFormat configurations
rg -n "dateFormat|timeAndDateFormat" --type ts -C2

Repository: RocketChat/Rocket.Chat

Length of output: 18451


Token map is missing mappings for tokens used in actual configurations.

The momentTokenMap lacks mappings for tokens that are actively used in the codebase:

  • MMM (abbreviated month) → should map to MMM — used in 'MMM D, YYYY' format (see worker.fixtures.ts, ChatTranscript.fixtures.ts)
  • H (24-hour no padding) → should map to H — used in 'H:mm:ss' format (line 149)
  • mm (minutes) → should map to mm — used in 'H:mm:ss' format
  • ss (seconds) → should map to ss — used in 'H:mm:ss' format

When momentFormatToDateFns() processes these formats, unmapped tokens will pass through unconverted, causing formatDate() to fail or produce incorrect output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.ts` around lines 10 -
22, The momentTokenMap is missing mappings for tokens used elsewhere (causing
unconverted tokens and format failures); update the momentTokenMap object to
include entries for 'MMM' -> 'MMM', 'H' -> 'H', 'mm' -> 'mm', and 'ss' -> 'ss'
so momentFormatToDateFns() can correctly translate formats used by formatDate()
and fixtures; locate and edit the momentTokenMap constant in ChatTranscript.ts
to add these key/value pairs.

Comment on lines +33 to +43
function formatInTimezone(ts: Date | number, timezone: string, fmt: string): string {
const d = new TZDate(new Date(ts).getTime(), timezone);
const y = d.getFullYear();
const m = d.getMonth();
const day = d.getDate();
const h = d.getHours();
const min = d.getMinutes();
const s = d.getSeconds();
const date = new Date(y, m, day, h, min, s);
return format(date, momentFormatToDateFns(fmt));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential issue: milliseconds are lost in timezone conversion.

The formatInTimezone function extracts date components from TZDate and creates a new plain Date, which discards milliseconds. While this is likely acceptable for display formatting, it's worth noting.

More importantly, creating a plain Date from the extracted components can cause issues near DST transitions because new Date(y, m, day, h, min, s) interprets these values in the local timezone of the server, not in the target timezone. If the server runs in a different timezone than the target, the formatted output could be incorrect.

🔧 Suggested fix using date-fns-tz format directly
+import { format as formatTz } from '@date-fns/tz';
+
 function formatInTimezone(ts: Date | number, timezone: string, fmt: string): string {
-	const d = new TZDate(new Date(ts).getTime(), timezone);
-	const y = d.getFullYear();
-	const m = d.getMonth();
-	const day = d.getDate();
-	const h = d.getHours();
-	const min = d.getMinutes();
-	const s = d.getSeconds();
-	const date = new Date(y, m, day, h, min, s);
-	return format(date, momentFormatToDateFns(fmt));
+	const d = new TZDate(new Date(ts).getTime(), timezone);
+	return formatTz(d, momentFormatToDateFns(fmt));
 }
Does date-fns format() function work correctly with TZDate from `@date-fns/tz`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/pdf-worker/src/strategies/ChatTranscript.ts` around lines 33 -
43, formatInTimezone currently builds a plain Date from TZDate components
(losing milliseconds and interpreting the result in the server's local
timezone), which can produce incorrect results around DST or when the server
timezone differs from the target; instead, stop extracting components and use
date-fns-tz's timezone-aware formatter (e.g., formatInTimeZone or format from
date-fns-tz) directly with the original timestamp/new Date(ts) and the converted
format (momentFormatToDateFns(fmt)) — update the formatInTimezone function to
call that timezone-aware formatter (keeping TZDate and manual reconstruction
out) so milliseconds are preserved and DST/timezone semantics are correct.

Comment on lines +17 to +18
type CronNextDate = { valueOf(): number };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd "Job.ts" packages/agenda

Repository: RocketChat/Rocket.Chat

Length of output: 164


🏁 Script executed:

cat -n packages/agenda/src/Job.ts | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 4110


🏁 Script executed:

# Check the cron package version in package.json
rg "cron" packages/agenda/package.json

Repository: RocketChat/Rocket.Chat

Length of output: 84


🏁 Script executed:

# Check cron types if available
fd -i "package.json" packages/agenda | xargs cat | jq '.dependencies, .devDependencies' 2>/dev/null

Repository: RocketChat/Rocket.Chat

Length of output: 265


🏁 Script executed:

fd -type f "\.d\.ts" packages/agenda/node_modules/cron/ 2>/dev/null | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if cron package has types bundled
find packages/agenda/node_modules/cron -name "*.d.ts" 2>/dev/null | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Try to locate type definition file for cron
rg "getNextDateFrom|_getNextDateFrom" packages/agenda/node_modules/cron/ --type typescript 2>/dev/null

Repository: RocketChat/Rocket.Chat

Length of output: 48


🌐 Web query:

cron npm package 4.4.0 CronTime getNextDateFrom API

💡 Result:

In cron npm package v4.4.0 (kelektiv/node-cron), CronTime#getNextDateFrom is an instance method with this TypeScript signature:

getNextDateFrom(start: Date | CustomDateTime, timeZone?: string | CustomZone): DateTime
  • start: a JS Date or a Luxon DateTime-like object (CustomDateTime).
  • timeZone (optional): either an IANA timezone string (e.g. "America/New_York") or a Luxon Zone-like object (CustomZone).
  • returns: a Luxon DateTime for the next scheduled occurrence after start. [1][2]

Example:

import { CronTime } from "cron";

const ct = new CronTime("*/5 * * * * *"); // every 5 seconds
const next = ct.getNextDateFrom(new Date());
console.log(next.toISO()); // Luxon DateTime

Sources: cron@4.4.0 API docs and type definitions. [1][2]

References

  1. https://www.jsdocs.io/package/cron
  2. https://unpkg.com/browse/cron@4.4.0/dist/time.d.ts

🌐 Web query:

Luxon DateTime valueOf method

💡 Result:

In Luxon, DateTime#valueOf() returns the Unix epoch timestamp in milliseconds (a number). It’s an alias of DateTime#toMillis(), so these are equivalent:

dt.valueOf()   // => epoch millis
dt.toMillis()  // => same value

Because valueOf() is used for numeric coercion in JavaScript, Luxon DateTimes can be compared/sorted numerically (e.g., a < b, a - b, or array.sort((a,b) => a - b)). The value represents the same instant in time regardless of the DateTime’s zone (changing the zone changes the representation, not the underlying epoch millis). [1] [2]

Sources:
[1] Luxon API docs (valueOf(): Returns the epoch milliseconds… Alias of DateTime#toMillis) (moment.github.io)
[2] Luxon API docs (same valueOf() / toMillis() description) (luxon.nodejs.cn)

Citations:


Switch to CronTime.getNextDateFrom() instead of the private _getNextDateFrom() method.

The cron 4.4.0 public API exposes CronTime.getNextDateFrom(...) and returns a Luxon DateTime. The current code relies on the private _getNextDateFrom method with a type shim, which is fragile after the major version bump from 1.8.2 to 4.4.0. The public method is type-safe and DateTime.valueOf() returns epoch milliseconds, making it compatible with the existing comparison logic.

Suggested fix
-type CronNextDate = { valueOf(): number };
-
 export class Job {
@@
-			let nextDate = cronTime._getNextDateFrom(lastRun, timezone ?? undefined) as CronNextDate;
+			let nextDate = cronTime.getNextDateFrom(lastRun, timezone ?? undefined);
 			if (nextDate.valueOf() === lastRun.getTime() || nextDate.valueOf() <= previousNextRunAt.getTime()) {
 				// Handle cronTime giving back the same date for the next run time
-				nextDate = cronTime._getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined) as CronNextDate;
+				nextDate = cronTime.getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined);
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type CronNextDate = { valueOf(): number };
export class Job {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agenda/src/Job.ts` around lines 17 - 18, The code uses the private
CronTime._getNextDateFrom and a fragile CronNextDate shim; change calls to the
public CronTime.getNextDateFrom(...) which returns a Luxon DateTime, and remove
or replace the CronNextDate type shim with the proper Luxon DateTime type (or
accept its valueOf()), then use dateTime.valueOf() where the code previously
used valueOf() on the shim. Update any occurrences in Job.ts that reference
_getNextDateFrom to call getNextDateFrom and adjust typings accordingly.

"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.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In date-fns v2+, which date/time format tokens are valid for localized and explicit formatting, and which Moment.js tokens (like L, LT, YYYY, DD) are incompatible or deprecated?

💡 Result:

date-fns v2+ valid format tokens

1) Localized “preset” tokens (date-fns-specific)

Use these in format() / parse() for locale-aware formatting:

  • PPPPP = localized date (short → full)
  • ppppp = localized time (short → full)
  • Combine freely (e.g. Pp, PPpp, PPPp, PPPPpppp).
    These P/p tokens are explicitly listed as date-fns additions for “long localized date/time”. [1]

2) Explicit tokens (Unicode TR-35, plus a few additions)

date-fns v2+ uses Unicode Technical Standard #35 field symbols (not Moment’s custom set) for explicit formatting—e.g. calendar year (y), month (M/L), day-of-month (d), weekday (E/e/c), hours/minutes/seconds (H/h/m/s), timezone (X/x/O/z), etc. The full accepted token table is in the format docs. [1]

Moment.js tokens that are incompatible (or commonly break in date-fns v2+)

A) Moment localized presets are NOT supported

Moment’s locale-aware presets:

  • L, LL, LLL, LLLL
  • LT, LTS
    are Moment-only. [2]

In date-fns, use P/p instead (see above). [1]

B) These Moment-style tokens are blocked by default in date-fns v2+ (and often mean something else)

date-fns treats these as different concepts than most Moment users expect, so it throws unless you opt in:

  • YYYY / YY in date-fns = local week-numbering year, not calendar year; prefer yyyy / yy. (Opt-in: useAdditionalWeekYearTokens: true.) [1][3]
  • DD / D in date-fns = day of year, not day of month; prefer dd / d. (Opt-in: useAdditionalDayOfYearTokens: true.) [1][3]

Quick “Moment → date-fns” mapping for the common ones you mentioned

  • L / LTP / p (and combos like Pp) [1][2]
  • YYYYyyyy (unless you really want week-year) [1][3]
  • DDdd (day of month) [1][3]

Sources:
[1] date-fns format token reference + notes (Unicode TR-35; P/p additions; errors for YYYY/DD) — https://date-fns.org/docs/format (mirrored) [turn1view0]
[2] Moment locale-aware preset tokens (LT, LTS, L, LL, LLL, LLLL) — https://momentjs.com/docs/#/displaying/format/ (mirrored) [turn0search3]
[3] date-fns unicodeTokens.md explaining DD/YYYY confusion and opt-in flags — https://github.com/date-fns/date-fns/blob/main/docs/unicodeTokens.md [turn2view0]


🏁 Script executed:

# Search for usages of these i18n keys in the codebase
rg -i "message_dateformat_description|message_timeformat_description" --type json -A 2 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 50378


🏁 Script executed:

# Look for other locale files and check if any have already been updated with date-fns tokens
find . -name "*.i18n.json" -type f | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 900


🏁 Script executed:

# Search for date-fns token usage patterns in the codebase
rg "format\(" --type ts --type tsx -A 2 | grep -E "(P{1,4}|p{1,4}|yyyy|dd|hh|HH)" | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Check the actual usage context - where these descriptions are displayed
rg "Message_DateFormat" --type ts --type tsx -B 3 -A 3

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

rg "Message_DateFormat_Description|Message_TimeFormat_Description" --type ts -B 3 -A 3

Repository: RocketChat/Rocket.Chat

Length of output: 893


🏁 Script executed:

# Search for date-fns usage and format string handling
rg "format\(" --type ts | grep -i "date-fns\|datefns" -A 2 -B 2 | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 660


🏁 Script executed:

# Check for migration-related comments or TODOs
rg "moment|Moment" --type ts -i | grep -E "(migrate|TODO|FIXME|date-fns)" -A 2 -B 2 | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look at the actual PR or changes related to date-fns migration
git log --oneline --all | grep -i "date-fns\|moment" | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 763


🏁 Script executed:

# Find the momentFormatToDateFns function
rg "momentFormatToDateFns" -A 10 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 2606


🏁 Script executed:

# Check the message settings file more closely
cat -n apps/meteor/server/settings/message.ts | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 3638


🏁 Script executed:

# Search for recent changes to i18n files related to this migration
git log --oneline --all -20 -- packages/i18n/src/locales/

Repository: RocketChat/Rocket.Chat

Length of output: 1554


🏁 Script executed:

# Find momentTokenMap definition
rg "momentTokenMap" -B 5 -A 30

Repository: RocketChat/Rocket.Chat

Length of output: 4860


🏁 Script executed:

# Check if there are multiple implementations or if this is used server-wide
rg "momentFormatToDateFns\|momentTokenMap" -l

Repository: RocketChat/Rocket.Chat

Length of output: 48


Update format descriptions to reflect date-fns standards

The descriptions still list Moment-style tokens (L, LL, YYYY, DD, LT, LTS) after the migration to date-fns. While a conversion utility exists in the PDF worker, these descriptions don't guide admins toward native date-fns tokens or clarify the conversion behavior. Update to show date-fns equivalents alongside or instead of Moment tokens.

💡 Suggested update
-  "Message_DateFormat_Description": "Tokens de formato: L, LL, LLL, YYYY, MM, DD, etc.",
+  "Message_DateFormat_Description": "Tokens de formato (date-fns): P, PP, PPP, yyyy, MM, dd, etc.",
...
-  "Message_TimeFormat_Description": "Tokens de formato: LT, LTS, H, h, mm, A, etc.",
+  "Message_TimeFormat_Description": "Tokens de formato (date-fns): p, pp, HH, hh, mm, a, etc.",

Also applies to: 3295-3295

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/i18n/src/locales/pt-BR.i18n.json` at line 3232, Update the locale
string for Message_DateFormat_Description (and the other affected key at the
same region) to replace Moment-style tokens with date-fns equivalents and/or
show both sets; reference the i18n key "Message_DateFormat_Description" and the
related key at the other occurrence and change the text to mention date-fns
tokens (e.g., yyyy, MM, dd, HH:mm:ss, etc.), note that a PDF worker conversion
utility will map legacy Moment tokens to date-fns when needed, and make the
description concise so admins are guided to use native date-fns tokens or
understand that automatic conversion occurs.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 82.14286% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.18%. Comparing base (9760d84) to head (7149865).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #40077      +/-   ##
===========================================
- Coverage    70.20%   70.18%   -0.02%     
===========================================
  Files         3278     3277       -1     
  Lines       116605   116591      -14     
  Branches     20723    20618     -105     
===========================================
- Hits         81859    81835      -24     
- Misses       31444    31474      +30     
+ Partials      3302     3282      -20     
Flag Coverage Δ
e2e 59.69% <ø> (+0.03%) ⬆️
e2e-api 46.68% <66.66%> (-0.78%) ⬇️
unit 70.99% <80.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ggazzo ggazzo force-pushed the chore/remove-moment-server branch 2 times, most recently from e982067 to 67581c9 Compare April 8, 2026 01:07
@ggazzo ggazzo requested review from a team as code owners April 8, 2026 01:07
@ggazzo ggazzo changed the base branch from develop to chore/remove-moment-client April 8, 2026 01:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/meteor/app/lib/server/functions/addUserToRoom.ts (1)

101-109: ⚠️ Potential issue | 🟠 Major

Don't let a stale delete turn the re-invite into a silent no-op.

removeByRoomIdAndUserId() can legitimately return null if the banned subscription disappears between the earlier lookup and the delete. In packages/models/src/models/Subscriptions.ts:1932-1946 it still calls Users.removeRoomByUserId(), so returning here skips Room.createUserSubscription() and can leave the user out of the room while reporting success. Only gate the removal notification/system message on deletedSub; keep the add flow going.

Suggested change
 		const deletedSub = await Subscriptions.removeByRoomIdAndUserId(rid, userToBeAdded._id);
-		if (!deletedSub) {
-			return true;
-		}
-		void notifyOnSubscriptionChanged(deletedSub, 'removed');
-
-		if (!skipSystemMessage && inviter) {
-			await Message.saveSystemMessage('user-unbanned', rid, userToBeAdded.username!, inviter, { ts: now });
+		if (deletedSub) {
+			void notifyOnSubscriptionChanged(deletedSub, 'removed');
+
+			if (!skipSystemMessage && inviter) {
+				await Message.saveSystemMessage('user-unbanned', rid, userToBeAdded.username!, inviter, { ts: now });
+			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/lib/server/functions/addUserToRoom.ts` around lines 101 -
109, The current early return when Subscriptions.removeByRoomIdAndUserId(rid,
userToBeAdded._1d) returns null can silently abort the re-add flow; instead,
only skip/guard the notifyOnSubscriptionChanged and Message.saveSystemMessage
calls when deletedSub is falsy but always proceed to call
Room.createUserSubscription(...) to add the user. Concretely, remove the early
"return true" after the delete, change the logic so that if (deletedSub) { void
notifyOnSubscriptionChanged(deletedSub, 'removed'); if (!skipSystemMessage &&
inviter) { await Message.saveSystemMessage(...); } } and then continue with the
existing add flow (Room.createUserSubscription and subsequent steps) so a stale
delete does not prevent re-inviting the user.
♻️ Duplicate comments (2)
apps/meteor/ee/server/lib/deviceManagement/session.ts (1)

58-77: ⚠️ Potential issue | 🟠 Major

Harden formatting for admin-provided date patterns.

Line 58 reads Message_TimeAndDateFormat, and Line 77 passes it directly to date-fns/format. If the setting still contains Moment-style tokens (LLL, YYYY, etc.), format can throw before reaching the Mailer.send try/catch.

Proposed fix
-		const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
+		const dateFormat = settings.get<string>('Message_TimeAndDateFormat') ?? 'yyyy-MM-dd HH:mm';
+		let formattedDate: string;
+		try {
+			formattedDate = format(new Date(), dateFormat);
+		} catch {
+			formattedDate = format(new Date(), 'yyyy-MM-dd HH:mm');
+		}
@@
-			date: format(new Date(), dateFormat),
+			date: formattedDate,
#!/bin/bash
set -euo pipefail

echo "== Setting references for Message_TimeAndDateFormat =="
rg -n -C2 "Message_TimeAndDateFormat" apps/meteor packages

echo
echo "== Current direct date-fns usage in session login mail data =="
rg -n -C2 "format\\(new Date\\(\\), dateFormat\\)" apps/meteor/ee/server/lib/deviceManagement/session.ts

echo
echo "== Potential Moment-style defaults/usages (e.g., LLL/YYYY/DD/Do) =="
rg -n -C2 "\\bLLL\\b|\\bYYYY\\b|\\bDD\\b|\\bDo\\b|moment\\(\\)\\.format" apps/meteor packages
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/deviceManagement/session.ts` around lines 58 - 77,
The code reads Message_TimeAndDateFormat into the dateFormat variable and passes
it directly to format(new Date(), dateFormat) which can throw for Moment-style
tokens; modify the session mail data construction to sanitize/validate/normalize
dateFormat before calling date-fns format (e.g., detect common Moment tokens and
either translate them to date-fns equivalents or reject them), and wrap the
format call in a small try/catch that falls back to a safe default ('yyyy-MM-dd
HH:mm') on error; look for the dateFormat variable and the format(new Date(),
dateFormat) expression in this file (and update the mail payload building logic
that sets mailData.date) to implement the validation+fallback.
packages/agenda/src/Job.ts (1)

17-18: ⚠️ Potential issue | 🔴 Critical

Use the public CronTime API instead of the private _getNextDateFrom shim.

Line 70 and Line 73 rely on a private method plus CronNextDate casting (Line 17), which is brittle after the cron major upgrade and can silently break scheduling behavior.

Suggested fix
-type CronNextDate = { valueOf(): number };
@@
-			let nextDate = cronTime._getNextDateFrom(lastRun, timezone ?? undefined) as CronNextDate;
+			let nextDate = cronTime.getNextDateFrom(lastRun, timezone ?? undefined);
 			if (nextDate.valueOf() === lastRun.getTime() || nextDate.valueOf() <= previousNextRunAt.getTime()) {
 				// Handle cronTime giving back the same date for the next run time
-				nextDate = cronTime._getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined) as CronNextDate;
+				nextDate = cronTime.getNextDateFrom(new Date(lastRun.getTime() + 1000), timezone ?? undefined);
 			}
#!/bin/bash
# Verify private vs public CronTime API usage and dependency version references.
rg -n --type=ts '\b_getNextDateFrom\s*\('
rg -n --type=ts '\bgetNextDateFrom\s*\('
rg -n '"cron"\s*:' packages/agenda/package.json
rg -n '_getNextDateFrom|getNextDateFrom' packages/agenda/src/definition/cron.d.ts
For npm package `cron` v4.4.x (kelektiv/cron), is `CronTime._getNextDateFrom` part of the public API, or should code use `CronTime.getNextDateFrom`?

Also applies to: 70-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agenda/src/Job.ts` around lines 17 - 18, The code is calling the
private CronTime._getNextDateFrom and using a fragile CronNextDate cast; replace
uses of _getNextDateFrom with the public CronTime.getNextDateFrom API and remove
the custom CronNextDate type. Locate occurrences of _getNextDateFrom in Job.ts
(references near the logic that computes next run time around the code that uses
CronTime and the CronNextDate cast) and swap them to call
CronTime.getNextDateFrom (adapting call signatures/return handling as needed),
then update any downstream code that assumed valueOf() to use the public Date
(or numeric) return from getNextDateFrom so scheduling remains stable across
cron major versions.
🧹 Nitpick comments (5)
apps/meteor/ee/server/lib/engagementDashboard/users.ts (1)

92-98: Validate and narrow createDestructuredDate input.

new Date(input) can produce Invalid Date; the current code would then return NaN date parts into downstream session queries. Since current call sites pass Date, narrowing the type and validating improves reliability.

♻️ Proposed refactor
-const createDestructuredDate = (input: Date | number): { year: number; month: number; day: number } => {
-	const date = new Date(input);
+const createDestructuredDate = (input: Date): { year: number; month: number; day: number } => {
+	if (!isValid(input)) {
+		throw new Error(`createDestructuredDate: invalid date received: ${String(input)}`);
+	}
 	return {
-		year: date.getFullYear(),
-		month: date.getMonth() + 1,
-		day: date.getDate(),
+		year: input.getFullYear(),
+		month: input.getMonth() + 1,
+		day: input.getDate(),
 	};
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts` around lines 92 - 98,
The createDestructuredDate function currently constructs new Date(input) which
can yield an Invalid Date and produce NaNs downstream; change the function
signature to accept a Date (not number) and inside validate the date by checking
isNaN(date.getTime()) (or similar) and throw a clear error if invalid, or
alternatively keep the union type but perform the same validation and throw;
update any call sites that pass numbers to provide Date objects so all callers
use the narrowed/validated createDestructuredDate.
apps/meteor/app/livechat/server/lib/sendTranscript.ts (1)

153-157: Move formatter creation outside the loop and add defensive date handling.

The Intl.DateTimeFormat instantiation should happen once before the loop rather than per message (lines 153–157). While getTimezone() guarantees valid IANA timezone strings, defensively handle the edge case of invalid message.ts by checking for NaN dates before formatting.

Suggested approach
+	const dateFormatter = new Intl.DateTimeFormat(userLanguage, {
+		timeZone: timezone,
+		dateStyle: 'long',
+		timeStyle: 'short',
+	});
+
 	for await (const message of messages) {
+		const messageDate = new Date(message.ts);
+		const datetime = Number.isNaN(messageDate.getTime()) ? '' : dateFormatter.format(messageDate);
-		const datetime = new Intl.DateTimeFormat(userLanguage, {
-			timeZone: timezone,
-			dateStyle: 'long',
-			timeStyle: 'short',
-		}).format(new Date(message.ts));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/lib/sendTranscript.ts` around lines 153 -
157, Move the Intl.DateTimeFormat creation out of the per-message loop by
creating a single formatter using userLanguage and timezone (used to compute
datetime) before iterating messages; inside the loop, first construct a Date
from message.ts and defensively check its validity (e.g., ensure
!isNaN(date.getTime())) before calling formatter.format, and handle invalid
timestamps by either skipping formatting or using a fallback string (so
references: message.ts, userLanguage, timezone, datetime, and the formatter
creation should be moved outside the loop that produces datetime).
apps/meteor/server/services/omnichannel-analytics/OverviewData.ts (1)

133-140: The DD-MM-YYYY ↔ YYYY-MM-DD round-trip works but creates format coupling.

The code converts the DD-MM-YYYY map key back to YYYY-MM-DD for parseRangeInTimezone. This works correctly, but creates implicit coupling between the format used at line 110 and the parsing logic here.

Consider extracting the format string as a constant or adding a brief comment to make the coupling explicit. This is optional since the current implementation is correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts` around
lines 133 - 140, The conversion from busiestDayFromMap ('DD-MM-YYYY') back to
'YYYY-MM-DD' for parseRangeInTimezone creates implicit format coupling; update
the code around busiestDay/busiestDayFromMap by extracting the date format(s)
into a named constant (e.g., DATE_KEY_FORMAT = 'DD-MM-YYYY' and ISO_DATE_FORMAT
= 'YYYY-MM-DD') or add a concise comment explaining the round-trip coupling,
then use the constant(s) when splitting/constructing the string passed to
parseRangeInTimezone and formatInTimezone so the intended formats are explicit
and easy to maintain.
apps/meteor/server/services/omnichannel-analytics/utils.ts (1)

59-64: Consider simplifying formatHourInTimezone - the timezone parameter has no effect.

The function creates a TZDate at the specified hour in the given timezone, then extracts that same hour value. Since getHours() on a TZDate returns the hour in its timezone, this always returns the input hour formatted as "12AM", "1AM", etc., regardless of which timezone is passed.

The function works correctly for its current usage (labeling hour slots), but the timezone parameter creates a misleading API. Consider either:

  1. Removing the timezone parameter since it's unused
  2. Documenting that the function simply formats an hour number without conversion
💡 Simplified version without unused timezone parameter
-export function formatHourInTimezone(hour: number, timezone: string): string {
-	const tzDate = new TZDate(2025, 0, 6, hour, 0, 0, 0, timezone);
-	const h = tzDate.getHours();
-	// eslint-disable-next-line no-nested-ternary
-	return h === 0 ? '12AM' : h < 12 ? `${h}AM` : h === 12 ? '12PM' : `${h - 12}PM`;
+export function formatHour12(hour: number): string {
+	const h = hour % 24;
+	if (h === 0) return '12AM';
+	if (h < 12) return `${h}AM`;
+	if (h === 12) return '12PM';
+	return `${h - 12}PM`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/server/services/omnichannel-analytics/utils.ts` around lines 59 -
64, formatHourInTimezone currently ignores the timezone argument
(TZDate/getHours just returns the input hour), so remove the unused timezone
parameter and the TZDate construction: change the signature to
formatHourInTimezone(hour: number): string, replace the TZDate logic with a
simple hour-to-label mapping (use the existing ternary logic on the input hour),
and update all call sites to drop the timezone argument; alternatively, if you
prefer to keep the name, add documentation clearly stating it only formats the
hour without timezone conversion.
apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts (1)

11-17: Consider extracting the UTC offset formatting to avoid duplication.

The offset string calculation duplicates the logic in AbstractBusinessHour.getUTCFromTimezone(). While getUTCFromTimezone is a protected method on a class, consider extracting it as a standalone utility function (similar to parseDayTimeInZone and formatUtcDayTime) to maintain DRY principles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`
around lines 11 - 17, The UTC offset string construction in
LivechatBusinessHours.ts duplicates logic from
AbstractBusinessHour.getUTCFromTimezone; extract that logic into a new utility
function (e.g., getUTCOffsetString(timezone?: string) alongside existing
utilities like parseDayTimeInZone and formatUtcDayTime), update
AbstractBusinessHour.getUTCFromTimezone to call the new utility, and replace the
inline offset calculation in LivechatBusinessHours (the const
tz/now/offsetMin/sign/h/m/utcStr block) to call the new utility instead to
eliminate duplication and keep behavior consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts`:
- Around line 155-163: The method formatDayOfTheWeekFromServerTimezoneAndUtcHour
uses new Date().getTimezoneOffset() which reflects the current DST state; change
the method to accept the business timezone name and compute the offset for the
specific UTC date using tzOffset(timezone, utc) (the same approach used by
getUTCFromTimezone), replace serverOffsetMs calculation with tzOffset(timezone,
utc) * 60_000, and update all callers to pass the timezone so the day/hour
conversion respects DST for the provided utc date.
- Around line 14-20: parseDayTimeInZone currently anchors to January 2025 which
produces a fixed (non-DST-aware) UTC offset; update it to compute the Date using
a real date that reflects the DST state (e.g. use the provided/current date or
accept an explicit date parameter) so offsets vary with DST. Locate
parseDayTimeInZone and change its signature to accept an optional Date (or
compute the next occurrence of the given weekday relative to today) and use that
year/month/day when constructing TZDate; then update callers such as
convertWorkHours and
startDaylightSavingTimeVerifier/hasDaylightSavingTimeChanged to pass the
current/reference date when recalculating/saving business hours so UTC times
reflect the correct DST offset. Ensure behavior preserves existing API when no
date is supplied by defaulting to the current date.

In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`:
- Around line 29-38: The cron fields for the default start/finish entries are
set with hardcoded day/time and must be generated the same way convertWorkHours
does: take the guessed timezone default times, parse them to UTC (as you already
do for startUtc/finishUtc), then call
formatDayOfTheWeekFromServerTimezoneAndUtcHour(utcDay, utcTime) to produce the
server-local cron day and time; replace the current cron: { dayOfWeek: day,
time: '08:00' } / { dayOfWeek: day, time: '20:00' } assignments with the values
derived from formatDayOfTheWeekFromServerTimezoneAndUtcHour for startUtc and
finishUtc so cron matches AbstractBusinessHour.convertWorkHours behavior.

In `@apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts`:
- Around line 298-300: The fire-and-forget call to
OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate) is
returning a promise that can reject but the surrounding try/catch won't catch
async rejections; change the call to handle promise rejections (either await the
call inside the try/catch or append .catch(...) to the returned promise) so any
error from scheduleInquiry (e.g., job.save()) is logged/handled instead of
becoming an unhandled rejection.

In `@apps/meteor/package.json`:
- Line 76: package.json contains a duplicate dependency entry for `@date-fns/tz`;
remove the later duplicate occurrence so there is only a single "@date-fns/tz"
entry (keep the existing one in proper alphabetical order) to avoid confusion
for package managers and maintain a clean dependencies list.

In `@ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts`:
- Around line 325-356: The teardown can skip deleteUser if one of the retry
blocks throws; wrap the two retry calls in a try/catch/finally (or surround each
retry with its own try/catch and run deleteUser in a finally) so that
deleteUser(rcUser, {}, rc1AdminRequestConfig) always runs regardless of retry
failures; in the catch log or swallow the retry error (include context like
'waiting for Synapse user displayname to propagate to RC' or 'waiting for
subscription fname...' and variables rcRoom._id, federationConfig.hs1.adminUser)
but do not rethrow so cleanup proceeds. Ensure references are to the existing
afterAll block, retry calls, getSubscriptionByRoomId, rcRoom, rcUserConfig, and
deleteUser so the change is easy to locate.
- Line 327: The test mutates the global Synapse admin identity by calling
hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); instead
create and use a dedicated test user for this spec: provision a new user (e.g.
via the same helper that creates test accounts), obtain its Matrix client
(instead of hs1AdminApp.matrixClient), perform setDisplayName on that client's
setDisplayName method and clean up the user after the test; replace all uses of
federationConfig.hs1.adminUser and hs1AdminApp.matrixClient in this test with
the dedicated test user's identifier and client to avoid shared-state coupling
and flakiness.

---

Outside diff comments:
In `@apps/meteor/app/lib/server/functions/addUserToRoom.ts`:
- Around line 101-109: The current early return when
Subscriptions.removeByRoomIdAndUserId(rid, userToBeAdded._1d) returns null can
silently abort the re-add flow; instead, only skip/guard the
notifyOnSubscriptionChanged and Message.saveSystemMessage calls when deletedSub
is falsy but always proceed to call Room.createUserSubscription(...) to add the
user. Concretely, remove the early "return true" after the delete, change the
logic so that if (deletedSub) { void notifyOnSubscriptionChanged(deletedSub,
'removed'); if (!skipSystemMessage && inviter) { await
Message.saveSystemMessage(...); } } and then continue with the existing add flow
(Room.createUserSubscription and subsequent steps) so a stale delete does not
prevent re-inviting the user.

---

Duplicate comments:
In `@apps/meteor/ee/server/lib/deviceManagement/session.ts`:
- Around line 58-77: The code reads Message_TimeAndDateFormat into the
dateFormat variable and passes it directly to format(new Date(), dateFormat)
which can throw for Moment-style tokens; modify the session mail data
construction to sanitize/validate/normalize dateFormat before calling date-fns
format (e.g., detect common Moment tokens and either translate them to date-fns
equivalents or reject them), and wrap the format call in a small try/catch that
falls back to a safe default ('yyyy-MM-dd HH:mm') on error; look for the
dateFormat variable and the format(new Date(), dateFormat) expression in this
file (and update the mail payload building logic that sets mailData.date) to
implement the validation+fallback.

In `@packages/agenda/src/Job.ts`:
- Around line 17-18: The code is calling the private CronTime._getNextDateFrom
and using a fragile CronNextDate cast; replace uses of _getNextDateFrom with the
public CronTime.getNextDateFrom API and remove the custom CronNextDate type.
Locate occurrences of _getNextDateFrom in Job.ts (references near the logic that
computes next run time around the code that uses CronTime and the CronNextDate
cast) and swap them to call CronTime.getNextDateFrom (adapting call
signatures/return handling as needed), then update any downstream code that
assumed valueOf() to use the public Date (or numeric) return from
getNextDateFrom so scheduling remains stable across cron major versions.

---

Nitpick comments:
In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`:
- Around line 11-17: The UTC offset string construction in
LivechatBusinessHours.ts duplicates logic from
AbstractBusinessHour.getUTCFromTimezone; extract that logic into a new utility
function (e.g., getUTCOffsetString(timezone?: string) alongside existing
utilities like parseDayTimeInZone and formatUtcDayTime), update
AbstractBusinessHour.getUTCFromTimezone to call the new utility, and replace the
inline offset calculation in LivechatBusinessHours (the const
tz/now/offsetMin/sign/h/m/utcStr block) to call the new utility instead to
eliminate duplication and keep behavior consistent.

In `@apps/meteor/app/livechat/server/lib/sendTranscript.ts`:
- Around line 153-157: Move the Intl.DateTimeFormat creation out of the
per-message loop by creating a single formatter using userLanguage and timezone
(used to compute datetime) before iterating messages; inside the loop, first
construct a Date from message.ts and defensively check its validity (e.g.,
ensure !isNaN(date.getTime())) before calling formatter.format, and handle
invalid timestamps by either skipping formatting or using a fallback string (so
references: message.ts, userLanguage, timezone, datetime, and the formatter
creation should be moved outside the loop that produces datetime).

In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts`:
- Around line 92-98: The createDestructuredDate function currently constructs
new Date(input) which can yield an Invalid Date and produce NaNs downstream;
change the function signature to accept a Date (not number) and inside validate
the date by checking isNaN(date.getTime()) (or similar) and throw a clear error
if invalid, or alternatively keep the union type but perform the same validation
and throw; update any call sites that pass numbers to provide Date objects so
all callers use the narrowed/validated createDestructuredDate.

In `@apps/meteor/server/services/omnichannel-analytics/OverviewData.ts`:
- Around line 133-140: The conversion from busiestDayFromMap ('DD-MM-YYYY') back
to 'YYYY-MM-DD' for parseRangeInTimezone creates implicit format coupling;
update the code around busiestDay/busiestDayFromMap by extracting the date
format(s) into a named constant (e.g., DATE_KEY_FORMAT = 'DD-MM-YYYY' and
ISO_DATE_FORMAT = 'YYYY-MM-DD') or add a concise comment explaining the
round-trip coupling, then use the constant(s) when splitting/constructing the
string passed to parseRangeInTimezone and formatInTimezone so the intended
formats are explicit and easy to maintain.

In `@apps/meteor/server/services/omnichannel-analytics/utils.ts`:
- Around line 59-64: formatHourInTimezone currently ignores the timezone
argument (TZDate/getHours just returns the input hour), so remove the unused
timezone parameter and the TZDate construction: change the signature to
formatHourInTimezone(hour: number): string, replace the TZDate logic with a
simple hour-to-label mapping (use the existing ternary logic on the input hour),
and update all call sites to drop the timezone argument; alternatively, if you
prefer to keep the name, add documentation clearly stating it only formats the
hour without timezone conversion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 062e1974-20c2-425c-adec-7d80024afb8b

📥 Commits

Reviewing files that changed from the base of the PR and between fcb678b and 67581c9.

📒 Files selected for processing (82)
  • .changeset/many-avocados-battle.md
  • .github/actions/meteor-build/action.yml
  • .github/actions/setup-node/action.yml
  • .github/actions/setup-playwright/action.yml
  • .github/workflows/ci-code-check.yml
  • .github/workflows/ci-test-e2e.yml
  • .github/workflows/ci.yml
  • .github/workflows/update-version-durability.yml
  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/app/lib/server/functions/addUserToRoom.ts
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts
  • apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts
  • apps/meteor/app/livechat/server/business-hour/Default.ts
  • apps/meteor/app/livechat/server/business-hour/Helper.ts
  • apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts
  • apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts
  • apps/meteor/app/livechat/server/lib/analytics/dashboards.ts
  • apps/meteor/app/livechat/server/lib/sendTranscript.ts
  • apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/ee/app/license/server/startup.ts
  • apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts
  • apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  • apps/meteor/ee/server/lib/deviceManagement/session.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • apps/meteor/package.json
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/server/lib/getMomentLocale.ts
  • apps/meteor/server/methods/loadLocale.ts
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/server/services/omnichannel-analytics/AgentData.ts
  • apps/meteor/server/services/omnichannel-analytics/OverviewData.ts
  • apps/meteor/server/services/omnichannel-analytics/service.ts
  • apps/meteor/server/services/omnichannel-analytics/utils.ts
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/tests/data/livechat/businessHours.ts
  • apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
  • apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
  • apps/meteor/tests/end-to-end/api/rooms.ts
  • apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts
  • apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts
  • apps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.ts
  • ee/apps/omnichannel-transcript/package.json
  • ee/apps/queue-worker/package.json
  • ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts
  • ee/packages/omnichannel-services/package.json
  • ee/packages/pdf-worker/package.json
  • ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts
  • ee/packages/pdf-worker/src/strategies/ChatTranscript.ts
  • packages/agenda/package.json
  • packages/agenda/src/Job.ts
  • packages/i18n/src/locales/en.i18n.json
  • packages/i18n/src/locales/es.i18n.json
  • packages/i18n/src/locales/hi-IN.i18n.json
  • packages/i18n/src/locales/pt-BR.i18n.json
  • packages/i18n/src/locales/zh.i18n.json
  • packages/message-types/package.json
  • packages/tools/package.json
  • packages/tools/src/timezone.ts
💤 Files with no reviewable changes (5)
  • ee/packages/omnichannel-services/package.json
  • ee/apps/queue-worker/package.json
  • packages/tools/package.json
  • apps/meteor/server/lib/getMomentLocale.ts
  • ee/apps/omnichannel-transcript/package.json
✅ Files skipped from review due to trivial changes (29)
  • .github/workflows/update-version-durability.yml
  • .changeset/many-avocados-battle.md
  • .github/actions/setup-playwright/action.yml
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • packages/message-types/package.json
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/app/livechat/server/business-hour/Default.ts
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • ee/packages/pdf-worker/src/strategies/ChatTranscript.spec.ts
  • packages/agenda/package.json
  • .github/workflows/ci-test-e2e.yml
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/server/methods/loadLocale.ts
  • apps/meteor/tests/unit/server/services/omnichannel-analytics/AgentData.tests.ts
  • packages/i18n/src/locales/zh.i18n.json
  • packages/i18n/src/locales/es.i18n.json
  • apps/meteor/tests/unit/server/services/omnichannel-analytics/OverviewData.tests.ts
  • packages/i18n/src/locales/hi-IN.i18n.json
  • packages/i18n/src/locales/pt-BR.i18n.json
  • packages/i18n/src/locales/en.i18n.json
🚧 Files skipped from review as they are similar to previous changes (25)
  • apps/meteor/app/livechat/server/business-hour/Helper.ts
  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • ee/packages/pdf-worker/package.json
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts
  • apps/meteor/app/livechat/server/statistics/LivechatAgentActivityMonitor.ts
  • apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
  • apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts
  • apps/meteor/app/livechat/server/lib/analytics/dashboards.ts
  • apps/meteor/tests/unit/app/livechat/server/business-hour/BusinessHourManager.spec.ts
  • apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts
  • apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts
  • packages/tools/src/timezone.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/server/services/omnichannel-analytics/AgentData.ts

Comment on lines +14 to +20
export function parseDayTimeInZone(day: string, timeStr: string, timezone: string): Date {
const [h = 0, m = 0] = timeStr.split(':').map(Number);
const dayIndex = DAY_NAMES.indexOf(day);
const refDate = 5 + dayIndex; // 2025-01-05 is Sunday
const tzDate = new TZDate(2025, 0, refDate, h, m, 0, timezone);
return new Date(tzDate.getTime());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for DST-related handling or verifier logic
rg -n -i 'dst|daylight|summer|winter' apps/meteor/app/livechat/server/business-hour/

Repository: RocketChat/Rocket.Chat

Length of output: 2277


🏁 Script executed:

# Get the hasDaylightSavingTimeChanged method implementation
sed -n '261,268p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts

Repository: RocketChat/Rocket.Chat

Length of output: 491


🏁 Script executed:

# Get the startDaylightSavingTimeVerifier method implementation
sed -n '273,285p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts

Repository: RocketChat/Rocket.Chat

Length of output: 633


🏁 Script executed:

# Check the broader context of DST verifier registration and invocation
sed -n '30,50p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts

Repository: RocketChat/Rocket.Chat

Length of output: 673


🏁 Script executed:

# Get the complete startDaylightSavingTimeVerifier method
sed -n '273,310p' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1282


🏁 Script executed:

# Check how business hours are used when creating cron jobs
rg -A 10 'createCronJobsForWorkHours' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 1075


🏁 Script executed:

# Check the LivechatBusinessHours model to see how times are stored
grep -n 'utc\|offset\|timezone' apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 503


🏁 Script executed:

# Find saveBusinessHour implementation - likely in the business hour type classes
rg -n 'saveBusinessHour' apps/meteor/app/livechat/server/business-hour/ --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 815


🏁 Script executed:

# Look for where UTC times are calculated/formatted from local times
rg -B 5 -A 5 'formatUtcDayTime\|parseDayTimeInZone' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Read the Default.ts saveBusinessHour implementation
sed -n '19,80p' apps/meteor/app/livechat/server/business-hour/Default.ts

Repository: RocketChat/Rocket.Chat

Length of output: 591


🏁 Script executed:

# Search for where UTC day/time conversions happen
rg -n 'start\.time\|finish\.time\|dayOfWeek' apps/meteor/app/livechat/server/business-hour/Default.ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look at AbstractBusinessHour.ts to see the helper functions
cat -n apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 3168


🏁 Script executed:

# Find baseSaveBusinessHour implementation
rg -n 'baseSaveBusinessHour' apps/meteor/app/livechat/server/business-hour/ -A 30 | head -60

Repository: RocketChat/Rocket.Chat

Length of output: 4353


🏁 Script executed:

# Also check if workHours UTC times are recalculated during save
rg -n 'workHours\|utc.*dayOfWeek' apps/meteor/app/livechat/server/business-hour/Default.ts -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Search for where UTC conversion happens when saving
rg -n 'parseDayTimeInZone\|formatUtcDayTime' apps/meteor/app/livechat/server/business-hour/ --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Verify the rest of convertWorkHours to see full context
sed -n '110,144p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1401


🏁 Script executed:

# Check if createCronJobsForWorkHours uses the UTC times or offset somehow differently
rg -n 'createCronJobsForWorkHours' apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts -A 20 | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 1444


🏁 Script executed:

# Get formatDayOfTheWeekFromServerTimezoneAndUtcHour implementation
sed -n '155,175p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 554


🏁 Script executed:

# Check if there are any tests that verify DST handling
find apps/meteor/app/livechat/server/business-hour -name '*.spec.ts' -o -name '*.test.ts' | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 155


🏁 Script executed:

# Look for any related issues or comments about DST in the codebase
rg -n 'TODO|FIXME|BUG|XXX' apps/meteor/app/livechat/server/business-hour/ | grep -i 'dst\|dst\|daylight\|timezone\|offset'

Repository: RocketChat/Rocket.Chat

Length of output: 48


The DST verifier exists but does not fix the underlying offset calculation issue.

While startDaylightSavingTimeVerifier() detects DST changes via hasDaylightSavingTimeChanged(), it does not solve the core problem. When DST is detected, the verifier re-saves business hours by calling convertWorkHours(), which recalculates UTC times using parseDayTimeInZone(). Since parseDayTimeInZone always anchors to January 2025 (a standard-time month), it produces identical UTC calculations regardless of the actual DST period.

Example: 08:00 in America/New_York will always convert to 13:00 UTC (EST offset), even when re-saved during EDT (when it should be 12:00 UTC). The verifier updates the offset string for display but leaves the UTC time calculations unchanged, causing business hours to be offset by ±1 hour for approximately 6 months per year in DST-observing timezones.

To fix this, parseDayTimeInZone() should use the actual current date or allow specifying an arbitrary date to capture the correct DST state, rather than a hardcoded January 2025 anchor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts` around
lines 14 - 20, parseDayTimeInZone currently anchors to January 2025 which
produces a fixed (non-DST-aware) UTC offset; update it to compute the Date using
a real date that reflects the DST state (e.g. use the provided/current date or
accept an explicit date parameter) so offsets vary with DST. Locate
parseDayTimeInZone and change its signature to accept an optional Date (or
compute the next occurrence of the given weekday relative to today) and use that
year/month/day when constructing TZDate; then update callers such as
convertWorkHours and
startDaylightSavingTimeVerifier/hasDaylightSavingTimeChanged to pass the
current/reference date when recalculating/saving business hours so UTC times
reflect the correct DST offset. Ensure behavior preserves existing API when no
date is supplied by defaulting to the current date.

Comment on lines 155 to 163
private formatDayOfTheWeekFromServerTimezoneAndUtcHour(utc: Date, fmt: 'dddd' | 'HH:mm'): string {
// Convert UTC date to server local timezone by adding the server's UTC offset
const serverOffsetMs = -new Date().getTimezoneOffset() * 60_000;
const local = new Date(utc.getTime() + serverOffsetMs);
if (fmt === 'dddd') {
return DAY_NAMES[local.getUTCDay()];
}
return `${String(local.getUTCHours()).padStart(2, '0')}:${String(local.getUTCMinutes()).padStart(2, '0')}`;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how cron.dayOfWeek and cron.time are consumed downstream
rg -n 'cron\.dayOfWeek|cron\.time' apps/meteor/app/livechat/server/business-hour/

Repository: RocketChat/Rocket.Chat

Length of output: 629


🏁 Script executed:

#!/bin/bash
# Find all usages of formatDayOfTheWeekFromServerTimezoneAndUtcHour
rg -n 'formatDayOfTheWeekFromServerTimezoneAndUtcHour' apps/meteor/app/livechat/server/business-hour/

Repository: RocketChat/Rocket.Chat

Length of output: 877


🏁 Script executed:

#!/bin/bash
# Examine the AbstractBusinessHour.ts file to understand the method and its context
wc -l apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 139


🏁 Script executed:

#!/bin/bash
# Look at the broader context around the method in question
sed -n '140,180p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 996


🏁 Script executed:

#!/bin/bash
# Check the imports and structure of the file
head -50 apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 2818


🏁 Script executed:

#!/bin/bash
# Get broader context around lines 130-139 to see where startUtc/finishUtc come from
sed -n '115,145p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1065


🏁 Script executed:

#!/bin/bash
# Search for where this method is being called (in which function)
sed -n '110,150p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1669


🏁 Script executed:

#!/bin/bash
# Look at how toMinutesSinceSunday uses cron fields and when scheduling happens
sed -n '30,70p' apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1328


🏁 Script executed:

#!/bin/bash
# Check the toMinutesSinceSunday function definition
rg -n 'function toMinutesSinceSunday|const toMinutesSinceSunday' apps/meteor/app/livechat/server/business-hour/

Repository: RocketChat/Rocket.Chat

Length of output: 223


🏁 Script executed:

#!/bin/bash
# Get the toMinutesSinceSunday and getCurrentDayAndTime function definitions
sed -n '1,30p' apps/meteor/app/livechat/server/business-hour/filterBusinessHoursThatMustBeOpened.ts

Repository: RocketChat/Rocket.Chat

Length of output: 913


🏁 Script executed:

#!/bin/bash
# Check the full method signature and class context - is timezone info available?
sed -n '100,165p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 2669


🏁 Script executed:

#!/bin/bash
# Check if this is the right method - verify it's not using any timezone context
ast-grep --pattern 'private formatDayOfTheWeekFromServerTimezoneAndUtcHour'

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if timezone name could be passed to the method
sed -n '109,135p' apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1108


The DST concern is valid. Fix the offset calculation to account for the UTC date being converted, not the current date.

The formatDayOfTheWeekFromServerTimezoneAndUtcHour method uses new Date().getTimezoneOffset() to calculate the offset, which reflects the current date's DST state. For UTC dates representing scheduled times, especially those across DST boundaries, this produces incorrect results. The method should accept the business hour's timezone name (available from the calling context) and use tzOffset(timezone, utc) to determine the correct offset for the specific date being converted, matching the pattern already used in getUTCFromTimezone nearby.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts` around
lines 155 - 163, The method formatDayOfTheWeekFromServerTimezoneAndUtcHour uses
new Date().getTimezoneOffset() which reflects the current DST state; change the
method to accept the business timezone name and compute the offset for the
specific UTC date using tzOffset(timezone, utc) (the same approach used by
getUTCFromTimezone), replace serverOffsetMs calculation with tzOffset(timezone,
utc) * 60_000, and update all callers to pass the timezone so the day/hour
conversion respects DST for the provided utc date.

Comment on lines 29 to 38
start: {
time: '08:00',
utc: { dayOfWeek: startUtc.ddd, time: startUtc.time },
cron: { dayOfWeek: day, time: '08:00' },
},
cron: {
dayOfWeek: moment(`${day}:20:00`, 'dddd:HH:mm').format('dddd'),
time: moment(`${day}:20:00`, 'dddd:HH:mm').format('HH:mm'),
finish: {
time: '20:00',
utc: { dayOfWeek: finishUtc.ddd, time: finishUtc.time },
cron: { dayOfWeek: day, time: '20:00' },
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how cron fields are consumed by the scheduler
rg -n -A5 'start\.cron|finish\.cron' apps/meteor/app/livechat/server/business-hour/

Repository: RocketChat/Rocket.Chat

Length of output: 1844


🏁 Script executed:

#!/bin/bash
# Find the implementation of formatDayOfTheWeekFromServerTimezoneAndUtcHour
rg -n 'formatDayOfTheWeekFromServerTimezoneAndUtcHour' apps/meteor/app/livechat/server/business-hour/ -A 10 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 3881


🏁 Script executed:

#!/bin/bash
# Find AbstractBusinessHour.convertWorkHours to see how it sets cron values
rg -n 'convertWorkHours' apps/meteor/app/livechat/server/business-hour/ -A 30 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 5220


🏁 Script executed:

#!/bin/bash
# Check the guessTimezone function and understand what timezone it produces
rg -n 'guessTimezone' apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts -B 5 -A 5

Repository: RocketChat/Rocket.Chat

Length of output: 856


The cron field values must be converted to server timezone to match AbstractBusinessHour.convertWorkHours behavior.

Currently, hardcoded day names and times (dayOfWeek: day, time: '08:00' / '20:00') are set without timezone conversion. However, in convertWorkHours, cron values are derived from UTC times converted to server-local timezone via formatDayOfTheWeekFromServerTimezoneAndUtcHour(). This inconsistency causes the default business hour's cron schedule to misalign with saved business hours when the server is not in UTC timezone.

Use the same conversion pattern: parse the default times in the guessed timezone to UTC, then convert those UTC values to server-local timezone for the cron fields—matching the pattern used in convertWorkHours lines 113–114 and 130–131.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/livechat/server/business-hour/LivechatBusinessHours.ts`
around lines 29 - 38, The cron fields for the default start/finish entries are
set with hardcoded day/time and must be generated the same way convertWorkHours
does: take the guessed timezone default times, parse them to UTC (as you already
do for startUtc/finishUtc), then call
formatDayOfTheWeekFromServerTimezoneAndUtcHour(utcDay, utcTime) to produce the
server-local cron day and time; replace the current cron: { dayOfWeek: day,
time: '08:00' } / { dayOfWeek: day, time: '20:00' } assignments with the values
derived from formatDayOfTheWeekFromServerTimezoneAndUtcHour for startUtc and
finishUtc so cron matches AbstractBusinessHour.convertWorkHours behavior.

Comment on lines 298 to 300
try {
void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, new Date(aggregatedDate.format()));
void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate);
} catch (e) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify async definition and the fire-and-forget call pattern.
rg -nP 'async\s+scheduleInquiry\s*\(' apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts
rg -nP 'try\s*\{\s*void\s+OmnichannelQueueInactivityMonitor\.scheduleInquiry\s*\(' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts

Repository: RocketChat/Rocket.Chat

Length of output: 139


🏁 Script executed:

# Read the relevant section of Helper.ts to see the actual try/catch code
sed -n '290,310p' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts

Repository: RocketChat/Rocket.Chat

Length of output: 778


🏁 Script executed:

# Get more context about the function to understand intent
sed -n '280,315p' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1115


🏁 Script executed:

# Check the implementation of scheduleInquiry to see what it might throw/reject
sed -n '77,120p' apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1372


🏁 Script executed:

# Check if there's any global unhandled rejection handler in the codebase
rg -l 'unhandledRejection|process\.on.*reject' --type ts --type js | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 349


🏁 Script executed:

# Verify the context where this function is called to confirm it's intentional fire-and-forget
sed -n '288,305p' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts

Repository: RocketChat/Rocket.Chat

Length of output: 689


🏁 Script executed:

# Check if there are similar fire-and-forget patterns elsewhere in the codebase to understand conventions
rg -B2 -A2 'void.*\(.*\)\.catch' apps/meteor/ee/app/livechat-enterprise/server/lib/ | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if there are other instances of fire-and-forget patterns with proper error handling in the codebase
rg -B1 -A3 'void.*\(.*\)' apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 373


🏁 Script executed:

# Check what types of errors scheduleInquiry or job.save() might throw
sed -n '77,90p' apps/meteor/ee/app/livechat-enterprise/server/lib/QueueInactivityMonitor.ts

Repository: RocketChat/Rocket.Chat

Length of output: 573


Unhandled promise rejection: try/catch does not catch async errors from scheduleInquiry.

Line 299 returns a promise without awaiting it, so the surrounding try/catch only catches synchronous throws. Any rejection from scheduleInquiry (e.g., from job.save()) will become an unhandled promise rejection.

Use .catch() to handle errors in the fire-and-forget pattern:

Suggested fix
 		try {
-			void OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate);
+			OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate).catch((e) => {
+				logger.error({ err: e });
+			});
 		} catch (e) {
 			// this will usually happen if other instance attempts to re-create a job
 			logger.error({ err: e });
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts` around lines 298
- 300, The fire-and-forget call to
OmnichannelQueueInactivityMonitor.scheduleInquiry(inq._id, aggregatedDate) is
returning a promise that can reject but the surrounding try/catch won't catch
async rejections; change the call to handle promise rejections (either await the
call inside the try/catch or append .catch(...) to the returned promise) so any
error from scheduleInquiry (e.g., job.save()) is logged/handled instead of
becoming an unhandled rejection.

Comment thread apps/meteor/package.json Outdated
Comment on lines +325 to +356
afterAll(async () => {
// Reset display name back to original before deleting the user
await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);

// wait until the name change is reflected in RC before finishing the test
await retry(
'waiting for Synapse user displayname to propagate to RC',
async () => {
const response = await rc1AdminRequestConfig.request
.get(api('users.info'))
.set(rc1AdminRequestConfig.credentials)
.query({ username: federationConfig.hs1.adminMatrixUserId })
.expect(200);

expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
},
{ retries: 15, delayMs: 1000 },
);

// Also wait for the DM subscription fname to be updated, since this propagates
// asynchronously after the user name change via debounced Room.updateDirectMessageRoomName
await retry(
'waiting for subscription fname to reflect reset display name',
async () => {
const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
},
{ retries: 10, delayMs: 1000 },
);

await deleteUser(rcUser, {}, rc1AdminRequestConfig);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make teardown failure-safe to avoid leaked state.

On Line 330-Line 353, a retry failure prevents Line 355 (deleteUser) from running. This can leave residual users and pollute subsequent tests.

Suggested hardening
 afterAll(async () => {
-	// Reset display name back to original before deleting the user
-	await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
-
-	// wait until the name change is reflected in RC before finishing the test
-	await retry(
-		'waiting for Synapse user displayname to propagate to RC',
-		async () => {
-			const response = await rc1AdminRequestConfig.request
-				.get(api('users.info'))
-				.set(rc1AdminRequestConfig.credentials)
-				.query({ username: federationConfig.hs1.adminMatrixUserId })
-				.expect(200);
-
-			expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
-		},
-		{ retries: 15, delayMs: 1000 },
-	);
-
-	// Also wait for the DM subscription fname to be updated, since this propagates
-	// asynchronously after the user name change via debounced Room.updateDirectMessageRoomName
-	await retry(
-		'waiting for subscription fname to reflect reset display name',
-		async () => {
-			const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
-			expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
-		},
-		{ retries: 10, delayMs: 1000 },
-	);
-
-	await deleteUser(rcUser, {}, rc1AdminRequestConfig);
+	try {
+		await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
+
+		await retry(
+			'waiting for Synapse user displayname to propagate to RC',
+			async () => {
+				const response = await rc1AdminRequestConfig.request
+					.get(api('users.info'))
+					.set(rc1AdminRequestConfig.credentials)
+					.query({ username: federationConfig.hs1.adminMatrixUserId })
+					.expect(200);
+
+				expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
+			},
+			{ retries: 15, delayMs: 1000 },
+		);
+
+		if (rcRoom?._id) {
+			await retry(
+				'waiting for subscription fname to reflect reset display name',
+				async () => {
+					const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
+					expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
+				},
+				{ retries: 10, delayMs: 1000 },
+			);
+		}
+	} finally {
+		await deleteUser(rcUser, {}, rc1AdminRequestConfig);
+	}
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
afterAll(async () => {
// Reset display name back to original before deleting the user
await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
// wait until the name change is reflected in RC before finishing the test
await retry(
'waiting for Synapse user displayname to propagate to RC',
async () => {
const response = await rc1AdminRequestConfig.request
.get(api('users.info'))
.set(rc1AdminRequestConfig.credentials)
.query({ username: federationConfig.hs1.adminMatrixUserId })
.expect(200);
expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
},
{ retries: 15, delayMs: 1000 },
);
// Also wait for the DM subscription fname to be updated, since this propagates
// asynchronously after the user name change via debounced Room.updateDirectMessageRoomName
await retry(
'waiting for subscription fname to reflect reset display name',
async () => {
const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
},
{ retries: 10, delayMs: 1000 },
);
await deleteUser(rcUser, {}, rc1AdminRequestConfig);
});
afterAll(async () => {
try {
await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
await retry(
'waiting for Synapse user displayname to propagate to RC',
async () => {
const response = await rc1AdminRequestConfig.request
.get(api('users.info'))
.set(rc1AdminRequestConfig.credentials)
.query({ username: federationConfig.hs1.adminMatrixUserId })
.expect(200);
expect(response.body.user).toHaveProperty('name', federationConfig.hs1.adminUser);
},
{ retries: 15, delayMs: 1000 },
);
if (rcRoom?._id) {
await retry(
'waiting for subscription fname to reflect reset display name',
async () => {
const sub = await getSubscriptionByRoomId(rcRoom._id, rcUserConfig.credentials, rcUserConfig.request);
expect(sub).toHaveProperty('fname', federationConfig.hs1.adminUser);
},
{ retries: 10, delayMs: 1000 },
);
}
} finally {
await deleteUser(rcUser, {}, rc1AdminRequestConfig);
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts` around lines 325
- 356, The teardown can skip deleteUser if one of the retry blocks throws; wrap
the two retry calls in a try/catch/finally (or surround each retry with its own
try/catch and run deleteUser in a finally) so that deleteUser(rcUser, {},
rc1AdminRequestConfig) always runs regardless of retry failures; in the catch
log or swallow the retry error (include context like 'waiting for Synapse user
displayname to propagate to RC' or 'waiting for subscription fname...' and
variables rcRoom._id, federationConfig.hs1.adminUser) but do not rethrow so
cleanup proceeds. Ensure references are to the existing afterAll block, retry
calls, getSubscriptionByRoomId, rcRoom, rcUserConfig, and deleteUser so the
change is easy to locate.


afterAll(async () => {
// Reset display name back to original before deleting the user
await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid mutating the shared Synapse admin identity in-test.

Line 369 changes the global HS1 admin display name, and Line 327 resets it later. This introduces shared-state coupling and can create flaky behavior when other federation tests use the same admin account concurrently or interleave unexpectedly.

Prefer creating a dedicated Synapse test user for this scenario and mutating only that user’s display name.

Also applies to: 369-369

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts` at line 327, The
test mutates the global Synapse admin identity by calling
hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminUser); instead
create and use a dedicated test user for this spec: provision a new user (e.g.
via the same helper that creates test accounts), obtain its Matrix client
(instead of hs1AdminApp.matrixClient), perform setDisplayName on that client's
setDisplayName method and clean up the user after the test; replace all uses of
federationConfig.hs1.adminUser and hs1AdminApp.matrixClient in this test with
the dedicated test user's identifier and client to avoid shared-state coupling
and flakiness.

@ggazzo ggazzo force-pushed the chore/remove-moment-server branch from 67581c9 to 62e5237 Compare April 8, 2026 01:23
@ggazzo ggazzo added this to the 8.4.0 milestone Apr 8, 2026
@ggazzo ggazzo changed the title chore: migrate server-side code from moment to date-fns chore: migrate simple server-side moment usages to date-fns Apr 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/i18n/src/locales/en.i18n.json (1)

5929-5930: ⚠️ Potential issue | 🟠 Major

Re-add Yesterday_at to prevent user-facing fallback text regression.

useTimeAgo still calls t('Yesterday_at') (apps/meteor/client/hooks/useTimeAgo.ts, Line 18). With this key missing, users can see literal Yesterday_at in timestamp labels.

💡 Proposed fix
   "Yesterday": "Yesterday",
+  "Yesterday_at": "Yesterday at",
   "You": "You",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/i18n/src/locales/en.i18n.json` around lines 5929 - 5930, Re-add the
missing "Yesterday_at" translation key used by useTimeAgo
(apps/meteor/client/hooks/useTimeAgo.ts calls t('Yesterday_at')); add the key to
en.i18n.json with a human-readable interpolated value that matches the hook's
expected token (e.g., "Yesterday at {{time}}") and propagate the same key to
other locale files to avoid fallback showing the literal key.
🧹 Nitpick comments (1)
packages/agenda/src/Job.ts (1)

59-61: Remove the dateForTimezone method or clarify its purpose.

The method returns its input unchanged (no-op) and is not called anywhere in the codebase. Either remove it entirely if it's no longer needed, or if it must remain for external API compatibility, rename it to reflect that it's a passthrough or add documentation explaining the change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agenda/src/Job.ts` around lines 59 - 61, The dateForTimezone method
on the Job class is a no-op and unused; remove it entirely if it's dead code, or
if it must remain for API compatibility keep the method but rename it to
indicate it's a passthrough (e.g., passthroughDateForTimezone) or add a
docstring above dateForTimezone explaining it intentionally returns the input
unchanged for backward compatibility; update any exported types/exports
referencing dateForTimezone accordingly and ensure tests/build still pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts`:
- Around line 61-62: The previous-period start calculation subtracts the
inclusive daysBetweenDates directly, causing an off-by-one too-early start; in
the block using endOfLastWeek and startOfLastWeek update the computation so
startOfLastWeek is moved by (daysBetweenDates - 1) days from endOfLastWeek
(i.e., subtract one less day) so the previous period covers the exact same
inclusive span as the current period (reference variables: endOfLastWeek,
startOfLastWeek, daysBetweenDates).

---

Outside diff comments:
In `@packages/i18n/src/locales/en.i18n.json`:
- Around line 5929-5930: Re-add the missing "Yesterday_at" translation key used
by useTimeAgo (apps/meteor/client/hooks/useTimeAgo.ts calls t('Yesterday_at'));
add the key to en.i18n.json with a human-readable interpolated value that
matches the hook's expected token (e.g., "Yesterday at {{time}}") and propagate
the same key to other locale files to avoid fallback showing the literal key.

---

Nitpick comments:
In `@packages/agenda/src/Job.ts`:
- Around line 59-61: The dateForTimezone method on the Job class is a no-op and
unused; remove it entirely if it's dead code, or if it must remain for API
compatibility keep the method but rename it to indicate it's a passthrough
(e.g., passthroughDateForTimezone) or add a docstring above dateForTimezone
explaining it intentionally returns the input unchanged for backward
compatibility; update any exported types/exports referencing dateForTimezone
accordingly and ensure tests/build still pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e4e348a4-81b7-4106-84c7-2911b6b5adbf

📥 Commits

Reviewing files that changed from the base of the PR and between 67581c9 and 62e5237.

📒 Files selected for processing (47)
  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/ee/app/license/server/startup.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  • apps/meteor/ee/server/lib/deviceManagement/session.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • apps/meteor/package.json
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/server/lib/getMomentLocale.ts
  • apps/meteor/server/methods/loadLocale.ts
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
  • ee/apps/omnichannel-transcript/package.json
  • ee/apps/queue-worker/package.json
  • ee/packages/omnichannel-services/package.json
  • ee/packages/pdf-worker/package.json
  • packages/agenda/package.json
  • packages/agenda/src/Job.ts
  • packages/i18n/src/locales/en.i18n.json
  • packages/i18n/src/locales/es.i18n.json
  • packages/i18n/src/locales/hi-IN.i18n.json
  • packages/i18n/src/locales/pt-BR.i18n.json
  • packages/i18n/src/locales/zh.i18n.json
  • packages/message-types/package.json
  • packages/tools/package.json
💤 Files with no reviewable changes (5)
  • ee/apps/omnichannel-transcript/package.json
  • ee/apps/queue-worker/package.json
  • ee/packages/omnichannel-services/package.json
  • packages/tools/package.json
  • apps/meteor/server/lib/getMomentLocale.ts
✅ Files skipped from review due to trivial changes (22)
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • packages/message-types/package.json
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  • apps/meteor/ee/server/lib/deviceManagement/session.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • packages/agenda/package.json
  • packages/i18n/src/locales/zh.i18n.json
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
  • apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
  • packages/i18n/src/locales/hi-IN.i18n.json
  • apps/meteor/ee/app/license/server/startup.ts
  • packages/i18n/src/locales/pt-BR.i18n.json
🚧 Files skipped from review as they are similar to previous changes (16)
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • ee/packages/pdf-worker/package.json
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/server/methods/loadLocale.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
  • packages/i18n/src/locales/es.i18n.json
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/package.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ✅ Tests Done
  • GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • packages/agenda/src/Job.ts
🧠 Learnings (11)
📓 Common learnings
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
📚 Learning: 2026-03-11T16:46:55.955Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:55.955Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2026-03-06T18:09:17.867Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • packages/agenda/src/Job.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • packages/agenda/src/Job.ts
📚 Learning: 2026-03-06T18:02:20.381Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2026-03-18T22:07:19.687Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/departments.ts:59-70
Timestamp: 2026-03-18T22:07:19.687Z
Learning: In `apps/meteor/app/apps/server/converters/departments.ts`, the `convertAppDepartment` method uses non-null assertions (`!`) on `department.name`, `department.email`, and `department.offlineMessageChannelName` when constructing `newDepartment: ILivechatDepartment`. These fields are optional on `IAppsDepartment`, but the App framework flow guarantees their presence at the call site. Do not flag these non-null assertions as unsafe during code review.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2026-02-10T16:32:49.806Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:49.806Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON which automatically deserializes Date objects. Stream handlers (e.g., sdk.stream()) receive Date fields as Date objects directly and do not require manual conversion using `new Date()`. Only REST API responses require manual date conversion because they return plain JSON where dates are serialized as strings.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
📚 Learning: 2025-11-19T18:20:07.720Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: packages/i18n/src/locales/en.i18n.json:918-921
Timestamp: 2025-11-19T18:20:07.720Z
Learning: Repo: RocketChat/Rocket.Chat — i18n/formatting
Learning: This repository uses a custom message formatting parser in UI blocks/messages; do not assume standard Markdown rules. For keys like Call_ended_bold, Call_not_answered_bold, Call_failed_bold, and Call_transferred_bold in packages/i18n/src/locales/en.i18n.json, retain the existing single-asterisk emphasis unless maintainers request otherwise.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • packages/i18n/src/locales/en.i18n.json
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • packages/agenda/src/Job.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • packages/agenda/src/Job.ts
🔇 Additional comments (13)
apps/meteor/app/cloud/server/functions/buildRegistrationData.ts (1)

72-72: Local timezone used for billing period - already flagged in prior review.

The concern about format(new Date(), 'yyyy-MM') using local server time instead of UTC for MAC billing has been comprehensively addressed in the existing review comment.

I'll note that markRoomResponded.ts (line 26-31 in relevant snippets) also uses format(new Date(), 'yyyy-MM') when writing activity periods, so reads and writes are at least internally consistent within this migration. However, for billing/licensing purposes, UTC consistency across different server timezones is typically required, which the prior comment's fix (new Date().toISOString().slice(0, 7)) would address for both locations.

packages/i18n/src/locales/en.i18n.json (1)

3433-3433: Date/time description updates look good.

These strings are clearer and remove hard dependency wording on Moment docs while keeping token guidance concise.

Also applies to: 3501-3501

packages/agenda/src/Job.ts (4)

17-18: Previously flagged: use of private _getNextDateFrom API.

The concern about using the private _getNextDateFrom method and the CronNextDate type shim has already been raised in a prior review. The public getNextDateFrom() method should be preferred for stability.


70-74: Previously flagged: prefer public getNextDateFrom() API.

This concern was already raised in a prior review. The private _getNextDateFrom method usage and type cast should be replaced with the public API.


67-67: LGTM!

Direct date assignment is correct since timezone handling is now delegated to CronTime._getNextDateFrom() via the second parameter.


84-87: LGTM!

Using getTime() instead of valueOf() is idiomatic for Date objects and both return identical epoch milliseconds values. The arithmetic logic is preserved correctly.

apps/meteor/ee/server/lib/engagementDashboard/users.ts (7)

3-3: Import migration is correct.

isValid, subDays, and subHours are appropriately scoped replacements for this file’s server-side date math.


12-14: Good guard for invalid createdAt.

Line 12 prevents persisting malformed analytics rows and avoids downstream date-int conversion issues.


29-29: Backfill window logic looks good.

Using subDays(date, 90) keeps the period explicit and aligns with fixed-day backfill behavior.


64-64: Yesterday boundary update is correct.

Line 64 keeps the intent clear and consistent with the date-fns migration.


92-97: Date destructuring refactor is clean.

The native Date extraction is straightforward, and month + 1 preserves 1-based month output.


133-133: 24-hour window calculation is clear and correct.

Using subHours(start, 24) reads directly as intended.


152-152: Weekly lookback migration is correct.

Line 152 keeps the existing “last 7 days” behavior explicit with date-fns.

Comment on lines +61 to +62
const endOfLastWeek = subDays(start, 1);
const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix off-by-one in previous-period start calculation.

Line 62 subtracts daysBetweenDates from endOfLastWeek, but daysBetweenDates is inclusive. That shifts the previous period one day too far back (e.g., 7-day range becomes an 8-day boundary window).

Proposed fix
 const endOfLastWeek = subDays(start, 1);
-const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates);
+const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const endOfLastWeek = subDays(start, 1);
const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates);
const endOfLastWeek = subDays(start, 1);
const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts` around lines 61 - 62,
The previous-period start calculation subtracts the inclusive daysBetweenDates
directly, causing an off-by-one too-early start; in the block using
endOfLastWeek and startOfLastWeek update the computation so startOfLastWeek is
moved by (daysBetweenDates - 1) days from endOfLastWeek (i.e., subtract one less
day) so the previous period covers the exact same inclusive span as the current
period (reference variables: endOfLastWeek, startOfLastWeek, daysBetweenDates).

Base automatically changed from chore/remove-moment-client to develop April 9, 2026 20:11
ggazzo and others added 3 commits April 10, 2026 17:55
Mechanical replacements across server code: moment() → new Date(),
.diff() → date-fns utilities, .format() → Intl.DateTimeFormat/format(),
dependency updates. No complex timezone logic changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ated

The timezone.ts rewrite is in the complex PR. This branch must keep
moment-timezone to avoid build failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cron upgrade and moment removal belong to the complex PR since business
hours and analytics still depend on them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ggazzo ggazzo force-pushed the chore/remove-moment-server branch from 308fd6f to 7149865 Compare April 10, 2026 20:56
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/meteor/app/statistics/server/lib/statistics.ts (1)

249-273: ⚠️ Potential issue | 🟠 Major

Preserve UTC semantics for MAC period and rolling windows.

Line 249 and Lines 257/264/271 now use local-time date-fns behavior, which can shift billing period and date-range boundaries by server timezone/DST. This regresses prior moment.utc() behavior and can miscount records for getMACStatisticsForPeriod / getMACStatisticsBetweenDates.

Proposed fix
-import { format, subDays } from 'date-fns';
@@
-		const billablePeriod = format(new Date(), 'yyyy-MM');
+		const now = new Date();
+		const billablePeriod = now.toISOString().slice(0, 7);
@@
-		const today = new Date();
-		const monthAgo = subDays(today, 30);
+		const today = now;
+		const monthAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
@@
-		const weekAgo = subDays(today, 7);
+		const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
@@
-		const yesterday = subDays(today, 1);
+		const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
#!/bin/bash
# Verify UTC expectations in tests and affected MAC call sites
rg -n "toISOString\\(\\)\\.slice\\(0, 7\\)|getMACStatisticsForPeriod|getMACStatisticsBetweenDates" \
  apps/meteor/tests/end-to-end/api/livechat/23-mac.ts \
  apps/meteor/app/statistics/server/lib/statistics.ts \
  packages/models/src/models/LivechatRooms.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/app/statistics/server/lib/statistics.ts` around lines 249 - 273,
The code currently builds billablePeriod and rolling-date boundaries using
local-time date-fns helpers which breaks prior UTC semantics; change
billablePeriod to derive the UTC year-month (e.g. const billablePeriod = new
Date().toISOString().slice(0,7)) and build the rolling-window dates passed to
LivechatRooms.getMACStatisticsBetweenDates using UTC-normalized Date objects
(create today/monthAgo/weekAgo/yesterday as UTC midnight via new
Date(Date.UTC(year, monthIndex, day)) or by truncating ISO strings to UTC
timestamps) so LivechatRooms.getMACStatisticsForPeriod and
getMACStatisticsBetweenDates receive UTC-based boundaries instead of local-time
values.
apps/meteor/ee/server/lib/engagementDashboard/users.ts (1)

60-64: ⚠️ Potential issue | 🟠 Major

Previous-period start is still off by one.

Same issue as before: daysBetweenDates is inclusive, so subtracting it directly makes the previous window one day too wide and distorts the period comparison.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts` around lines 60 - 64,
The previous-period range is one day too long because daysBetweenDates is
computed inclusive by diffBetweenDaysInclusive; when computing startOfLastWeek
(and endOfLastWeek) use an exclusive-length offset: set endOfLastWeek =
subDays(start, 1) (keep) but compute startOfLastWeek by subtracting
(daysBetweenDates - 1) rather than daysBetweenDates, i.e. use
subDays(endOfLastWeek, daysBetweenDates - 1) so the previous window matches the
original interval length; ensure any uses of daysBetweenDates for window spans
account for the inclusive calculation from diffBetweenDaysInclusive.
🧹 Nitpick comments (1)
apps/meteor/ee/server/lib/engagementDashboard/date.ts (1)

27-30: Make the day-diff helper’s contract match how it is actually used.

This helper is declared as (start, end), but the dashboard callers pass (end, start) to get a positive span. Renaming the parameters to something like later / earlier would make the API much harder to misuse.

♻️ Proposed refactor
-const diffBetweenDays = (start: string | number | Date, end: string | number | Date): number =>
-	differenceInDays(new Date(start), new Date(end));
-export const diffBetweenDaysInclusive = (start: string | number | Date, end: string | number | Date): number =>
-	diffBetweenDays(start, end) + 1;
+const diffBetweenDays = (later: string | number | Date, earlier: string | number | Date): number =>
+	differenceInDays(new Date(later), new Date(earlier));
+export const diffBetweenDaysInclusive = (later: string | number | Date, earlier: string | number | Date): number =>
+	diffBetweenDays(later, earlier) + 1;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts` around lines 27 - 30,
Rename the parameters on diffBetweenDays and diffBetweenDaysInclusive from
(start, end) to (later, earlier) to match how callers pass arguments; update
diffBetweenDays to call differenceInDays(new Date(later), new Date(earlier)) so
the result is positive for a later date first, and update
diffBetweenDaysInclusive to accept (later, earlier) and return
diffBetweenDays(later, earlier) + 1; keep function names diffBetweenDays and
diffBetweenDaysInclusive the same so callers work without further changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/meteor/ee/server/lib/engagementDashboard/messages.ts`:
- Around line 75-79: The previous-period range is off-by-one because
daysBetweenDates from diffBetweenDaysInclusive is already inclusive; adjust the
back-calculation so the previous window has the same length as the current
window by subtracting (daysBetweenDates - 1) instead of daysBetweenDates when
computing startOfLastWeek (i.e., use subDays(endOfLastWeek, daysBetweenDates -
1)); update any related comments and keep references to daysBetweenDates,
endOfLastWeek, startOfLastWeek, and diffBetweenDaysInclusive so reviewers can
verify the inclusive range now matches the current period exactly.

---

Duplicate comments:
In `@apps/meteor/app/statistics/server/lib/statistics.ts`:
- Around line 249-273: The code currently builds billablePeriod and rolling-date
boundaries using local-time date-fns helpers which breaks prior UTC semantics;
change billablePeriod to derive the UTC year-month (e.g. const billablePeriod =
new Date().toISOString().slice(0,7)) and build the rolling-window dates passed
to LivechatRooms.getMACStatisticsBetweenDates using UTC-normalized Date objects
(create today/monthAgo/weekAgo/yesterday as UTC midnight via new
Date(Date.UTC(year, monthIndex, day)) or by truncating ISO strings to UTC
timestamps) so LivechatRooms.getMACStatisticsForPeriod and
getMACStatisticsBetweenDates receive UTC-based boundaries instead of local-time
values.

In `@apps/meteor/ee/server/lib/engagementDashboard/users.ts`:
- Around line 60-64: The previous-period range is one day too long because
daysBetweenDates is computed inclusive by diffBetweenDaysInclusive; when
computing startOfLastWeek (and endOfLastWeek) use an exclusive-length offset:
set endOfLastWeek = subDays(start, 1) (keep) but compute startOfLastWeek by
subtracting (daysBetweenDates - 1) rather than daysBetweenDates, i.e. use
subDays(endOfLastWeek, daysBetweenDates - 1) so the previous window matches the
original interval length; ensure any uses of daysBetweenDates for window spans
account for the inclusive calculation from diffBetweenDaysInclusive.

---

Nitpick comments:
In `@apps/meteor/ee/server/lib/engagementDashboard/date.ts`:
- Around line 27-30: Rename the parameters on diffBetweenDays and
diffBetweenDaysInclusive from (start, end) to (later, earlier) to match how
callers pass arguments; update diffBetweenDays to call differenceInDays(new
Date(later), new Date(earlier)) so the result is positive for a later date
first, and update diffBetweenDaysInclusive to accept (later, earlier) and return
diffBetweenDays(later, earlier) + 1; keep function names diffBetweenDays and
diffBetweenDaysInclusive the same so callers work without further changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7e9a21ca-007a-46dd-8aba-5e310114de5c

📥 Commits

Reviewing files that changed from the base of the PR and between 62e5237 and 7149865.

📒 Files selected for processing (34)
  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/ee/app/license/server/startup.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  • apps/meteor/ee/server/lib/deviceManagement/session.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/server/lib/getMomentLocale.ts
  • apps/meteor/server/methods/loadLocale.ts
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
  • ee/apps/omnichannel-transcript/package.json
💤 Files with no reviewable changes (2)
  • ee/apps/omnichannel-transcript/package.json
  • apps/meteor/server/lib/getMomentLocale.ts
✅ Files skipped from review due to trivial changes (9)
  • apps/meteor/tests/end-to-end/api/livechat/23-mac.ts
  • apps/meteor/server/services/nps/notification.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoTransferChatsScheduler.tests.ts
  • apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/hooks/afterInquiryQueued.spec.ts
  • apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  • apps/meteor/app/utils/server/lib/getTimezone.ts
  • apps/meteor/app/smarsh-connector/server/functions/generateEml.ts
  • apps/meteor/server/lib/dataExport/processDataDownloads.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/afterInquiryQueued.ts
  • apps/meteor/ee/app/livechat-enterprise/server/hooks/setPredictedVisitorAbandonmentTime.ts
  • apps/meteor/ee/app/license/server/startup.ts
  • apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
  • apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
  • apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts
  • apps/meteor/app/irc/server/irc-bridge/index.js
  • apps/meteor/app/lib/server/lib/processDirectEmail.ts
  • apps/meteor/server/settings/smarsh.ts
  • apps/meteor/server/services/omnichannel/service.ts
  • apps/meteor/app/lib/server/methods/updateMessage.ts
  • apps/meteor/ee/server/lib/deviceManagement/session.ts
  • apps/meteor/server/lib/dataExport/sendViaEmail.ts
  • apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
  • apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
  • apps/meteor/server/methods/loadLocale.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
🧠 Learnings (26)
📓 Common learnings
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
📚 Learning: 2026-03-06T18:09:17.867Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/DateTimeFormats.spec.tsx:20-23
Timestamp: 2026-03-06T18:09:17.867Z
Learning: In the RocketChat/Rocket.Chat gazzodown package (`packages/gazzodown`), tests are intended to run under the UTC timezone, but as of PR `#39397` this is NOT yet explicitly enforced in `jest.config.ts` or the `package.json` test scripts (which just run `jest` without `TZ=UTC`). To make timezone-sensitive snapshot tests reliable across all environments, `TZ=UTC` should be added to the test scripts in `package.json` or to `jest.config.ts` via `testEnvironmentOptions.timezone`. Without explicit UTC enforcement, snapshot tests involving date-fns formatted output or `toLocaleString()` will fail for contributors in non-UTC timezones.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-16T22:56:54.500Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39677
File: packages/models/src/helpers/omnichannel/agentStatus.ts:10-29
Timestamp: 2026-03-16T22:56:54.500Z
Learning: In `packages/models/src/helpers/omnichannel/agentStatus.ts` (PR `#39677`), the `queryStatusAgentOnline` function intentionally omits the `$or` offline-status guard for non-bot agents when `isLivechatEnabledWhenAgentIdle === true`. This is by design: the setting `Livechat_enabled_when_agent_idle` (`accept_chats_when_agent_idle`) means agents should receive chats even when idle/offline, so the offline filter must be removed in that path. Bots are always status-agnostic and are always included regardless of their online/offline status. Do not flag this as a bug.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
📚 Learning: 2026-03-23T19:33:46.159Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 39818
File: apps/meteor/client/hooks/usePruneWarningMessage.ts:45-56
Timestamp: 2026-03-23T19:33:46.159Z
Learning: In RocketChat/Rocket.Chat, cron expressions used by the retention policy (e.g., `RetentionPolicy_Advanced_Precision_Cron`) follow the convention of the `cron` npm package (outdated version), which uses **0-indexed months** (0 = January, 11 = December), not the standard 1-12 used in POSIX cron. The local `sendAt` function in `apps/meteor/client/hooks/usePruneWarningMessage.ts` intentionally replicates this 0-indexed behavior. Do NOT suggest adding `+1` to `date.getMonth()` in this file, as it would break tests and create inconsistency with the backend.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2026-02-10T16:32:49.806Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:49.806Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON which automatically deserializes Date objects. Stream handlers (e.g., sdk.stream()) receive Date fields as Date objects directly and do not require manual conversion using `new Date()`. Only REST API responses require manual date conversion because they return plain JSON where dates are serialized as strings.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts
  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
  • apps/meteor/ee/server/lib/engagementDashboard/date.ts
  • apps/meteor/ee/server/lib/engagementDashboard/users.ts
  • apps/meteor/ee/server/lib/engagementDashboard/messages.ts
📚 Learning: 2026-03-11T16:46:55.955Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39535
File: apps/meteor/app/apps/server/bridges/livechat.ts:249-249
Timestamp: 2026-03-11T16:46:55.955Z
Learning: In `apps/meteor/app/apps/server/bridges/livechat.ts`, `createVisitor()` intentionally does not propagate `externalIds` to `registerData`. This is by design: the method is deprecated (JSDoc: `deprecated Use createAndReturnVisitor instead. Note: This method does not support externalIds.`) to push callers toward `createAndReturnVisitor()`, which does support `externalIds`. Do not flag the missing `externalIds` field in `createVisitor()` as a bug.

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
📚 Learning: 2026-03-06T18:02:20.381Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/elements/Timestamp/RelativeTime.spec.tsx:63-70
Timestamp: 2026-03-06T18:02:20.381Z
Learning: In RocketChat/Rocket.Chat, tests in the `packages/gazzodown` package (and likely the broader test suite) are always expected to run in the UTC timezone. This makes `toLocaleString()` output deterministic, so snapshot tests that include locale/timezone-sensitive content (such as `title` attributes from `toLocaleString()`) are stable and do not need to be replaced with targeted assertions.

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-16T11:50:18.066Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 39657
File: apps/meteor/tests/end-to-end/apps/app-resolve-visitor.ts:18-21
Timestamp: 2026-03-16T11:50:18.066Z
Learning: In `apps/meteor/tests/end-to-end/apps/` (and Livechat E2E tests broadly), calling `createAgent()` and `makeAgentAvailable()` immediately after `updateSetting('Livechat_enabled', true)` — without any intermediate sleep — is an established, non-flaky pattern used across 10+ tests in the codebase. Do not flag this sequence as a potential race condition or suggest adding a delay between them.

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-18T22:07:19.687Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/departments.ts:59-70
Timestamp: 2026-03-18T22:07:19.687Z
Learning: In `apps/meteor/app/apps/server/converters/departments.ts`, the `convertAppDepartment` method uses non-null assertions (`!`) on `department.name`, `department.email`, and `department.offlineMessageChannelName` when constructing `newDepartment: ILivechatDepartment`. These fields are optional on `IAppsDepartment`, but the App framework flow guarantees their presence at the call site. Do not flag these non-null assertions as unsafe during code review.

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
📚 Learning: 2026-02-25T20:10:16.987Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38913
File: packages/ddp-client/src/legacy/types/SDKLegacy.ts:34-34
Timestamp: 2026-02-25T20:10:16.987Z
Learning: In the RocketChat/Rocket.Chat monorepo, packages/ddp-client and apps/meteor do not use TypeScript project references. Module augmentations in apps/meteor (e.g., declare module 'rocket.chat/rest-typings') are not visible when compiling packages/ddp-client in isolation, which is why legacy SDK methods that depend on OperationResult types for OpenAPI-migrated endpoints must remain commented out.

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
  • apps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2025-11-19T18:20:07.720Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: packages/i18n/src/locales/en.i18n.json:918-921
Timestamp: 2025-11-19T18:20:07.720Z
Learning: Repo: RocketChat/Rocket.Chat — i18n/formatting
Learning: This repository uses a custom message formatting parser in UI blocks/messages; do not assume standard Markdown rules. For keys like Call_ended_bold, Call_not_answered_bold, Call_failed_bold, and Call_transferred_bold in packages/i18n/src/locales/en.i18n.json, retain the existing single-asterisk emphasis unless maintainers request otherwise.

Applied to files:

  • apps/meteor/app/statistics/server/lib/statistics.ts
📚 Learning: 2026-03-02T16:31:41.304Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 39250
File: apps/meteor/tests/end-to-end/api/livechat/07-queue.ts:1084-1094
Timestamp: 2026-03-02T16:31:41.304Z
Learning: In E2E API tests at apps/meteor/tests/end-to-end/api/livechat/, using sleep(1000) after updateSetting() or updateEESetting() calls in test setup hooks is acceptable and intentional to allow omnichannel settings to propagate their side effects.

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (`test`, `page`, `expect`) for consistency in test files

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `page.waitFor()` with specific conditions instead of hardcoded timeouts in Playwright tests

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Implement proper wait strategies for dynamic content in Playwright tests

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `expect` matchers for assertions (`toEqual`, `toContain`, `toBeTruthy`, `toHaveLength`, etc.) instead of `assert` statements in Playwright tests

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-12-10T21:00:54.909Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 37091
File: ee/packages/abac/jest.config.ts:4-7
Timestamp: 2025-12-10T21:00:54.909Z
Learning: Rocket.Chat monorepo: Jest testMatch pattern '<rootDir>/src/**/*.spec.(ts|js|mjs)' is valid in this repo and used across multiple packages (e.g., packages/tools, ee/packages/omnichannel-services). Do not flag it as invalid in future reviews.

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2025-12-16T17:29:45.163Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37834
File: apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts:12-22
Timestamp: 2025-12-16T17:29:45.163Z
Learning: In page object files under `apps/meteor/tests/e2e/page-objects/`, always import `expect` from `../../utils/test` (Playwright's async expect), not from Jest. Jest's `expect` has a synchronous signature and will cause TypeScript errors when used with web-first assertions like `toBeVisible()`.

Applied to files:

  • apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.

Applied to files:

  • apps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2026-02-24T19:16:35.307Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 39003
File: apps/meteor/client/lib/chats/flows/sendMessage.ts:39-45
Timestamp: 2026-02-24T19:16:35.307Z
Learning: In apps/meteor/client/lib/chats/flows/sendMessage.ts, when sdk.call('sendMessage', ...) throws an error, the message is intentionally left with temp: true (not deleted or cleaned up) to support a future retry UI feature. This allows users to retry sending failed messages rather than losing them.

Applied to files:

  • apps/meteor/app/lib/server/methods/sendMessage.ts
📚 Learning: 2026-03-03T11:11:48.541Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 39230
File: apps/meteor/app/api/server/v1/chat.ts:214-222
Timestamp: 2026-03-03T11:11:48.541Z
Learning: In apps/meteor/server/lib/moderation/reportMessage.ts, the reportMessage function validates that description is not empty or whitespace-only with `if (!description.trim())`. When migrating the chat.reportMessage endpoint to OpenAPI, adding minLength validation to the schema preserves this existing behavior.

Applied to files:

  • apps/meteor/app/lib/server/methods/sendMessage.ts
🔇 Additional comments (3)
apps/meteor/app/lib/server/methods/sendMessage.ts (1)

52-52: Timestamp diff replacement looks correct.

Line 52 preserves the same millisecond delta behavior used by the existing 60s/10s guardrails.

apps/meteor/ee/app/livechat-enterprise/server/lib/AutoCloseOnHoldScheduler.ts (1)

5-5: Mechanical migration looks correct.

addSeconds(new Date(), timeout) preserves the previous “schedule at now + timeout seconds” behavior while still passing a concrete Date into Agenda.

Also applies to: 53-53

apps/meteor/ee/tests/unit/apps/livechat-enterprise/lib/AutoCloseOnHold.tests.ts (1)

3-3: Test stays aligned with the production calculation.

Using addSeconds(new Date(), 5) here keeps the expectation consistent with scheduleRoom, and the existing tolerance still makes the assertion robust.

Also applies to: 136-142

Comment on lines 75 to +79
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));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the previous-period range off by one.

daysBetweenDates is already inclusive, so subtracting it directly makes the comparison window one day longer than the current window. A 7-day range turns into an 8-day previous period here, which skews period.variation.

🐛 Proposed fix
 	const daysBetweenDates = diffBetweenDaysInclusive(end, start);
 	const endOfLastWeek = subDays(start, 1);
-	const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates);
+	const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 daysBetweenDates = diffBetweenDaysInclusive(end, start);
const endOfLastWeek = subDays(start, 1);
const startOfLastWeek = subDays(endOfLastWeek, daysBetweenDates - 1);
const today = convertDateToInt(end);
const yesterday = convertDateToInt(subDays(end, 1));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/ee/server/lib/engagementDashboard/messages.ts` around lines 75 -
79, The previous-period range is off-by-one because daysBetweenDates from
diffBetweenDaysInclusive is already inclusive; adjust the back-calculation so
the previous window has the same length as the current window by subtracting
(daysBetweenDates - 1) instead of daysBetweenDates when computing
startOfLastWeek (i.e., use subDays(endOfLastWeek, daysBetweenDates - 1)); update
any related comments and keep references to daysBetweenDates, endOfLastWeek,
startOfLastWeek, and diffBetweenDaysInclusive so reviewers can verify the
inclusive range now matches the current period exactly.

@ggazzo ggazzo modified the milestones: 8.4.0, 8.5.0 Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant