RocketChat/Rocket.Chat · error · Error

invalid ISO 8601 date

Error message

invalid ISO 8601 date

What it means

mapDateForAPI validates engagement-dashboard date parameters by round-trip: it parses the string with Date.parse and requires new Date(timestamp).toISOString() to equal the input exactly. Only the canonical UTC form with milliseconds and a Z suffix (e.g. 2024-01-01T00:00:00.000Z) passes. Date-only strings, offsets like +02:00, missing milliseconds, or trailing characters all throw 'invalid ISO 8601 date'.

Solutions

  1. Send dates produced by new Date(...).toISOString(), which always yields UTC with .SSSZ
  2. If using moment: moment(date).toISOString()
  3. Validate input with the exported isDateISOString helper (or an equivalent round-trip check) before calling mapDateForAPI

Example fix

// before
mapDateForAPI('2024-01-01T00:00:00+00:00'); // throws

// after
mapDateForAPI(new Date('2024-01-01T00:00:00+00:00').toISOString()); // '2024-01-01T00:00:00.000Z'
Defensive patterns

Strategy: type-guard

Validate before calling

const start = new Date(Date.now() - 86400000).toISOString();
const end = new Date().toISOString();
if (!isISODateString(start) || !isISODateString(end)) {
	throw new TypeError('dates must be canonical ISO 8601 UTC strings');
}
const range = { start: mapDateForAPI(start), end: mapDateForAPI(end) };

Type guard

const isISODateString = (input: string): input is string => {
	const timestamp = Date.parse(input);
	return !Number.isNaN(timestamp) && new Date(timestamp).toISOString() === input;
};

Try / catch

try {
	const from = mapDateForAPI(raw);
} catch (err) {
	if (err instanceof Error && err.message === 'invalid ISO 8601 date') {
		// reject the request with a 400 and a hint to use toISOString()
	} else {
		throw err;
	}
}

Prevention

When it happens

Trigger: Calling an engagement dashboard endpoint (apps/meteor ee engagement dashboard handlers that use mapDateForAPI) with start/end params such as '2024-01-01' or '2024-01-01T00:00:00+00:00' instead of '2024-01-01T00:00:00.000Z'.

Common situations: Hand-built query strings; serializing dates with moment().format('YYYY-MM-DDTHH:mm:ss') without millisecond/Z precision; passing timestamps from systems that emit offset-based ISO strings.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/7d8e322ee9149ac9. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/ee/server/lib/engagementDashboard/date.ts:14

import mem from 'mem';
import moment from 'moment';

export const isDateISOString = mem(
	(input: string): input is string => {
		const timestamp = Date.parse(input);
		return !Number.isNaN(timestamp) && new Date(timestamp).toISOString() === input;
	},
	{ maxAge: 10000 },
);

export const mapDateForAPI = (input: string): Date => {
	if (!isDateISOString(input)) {
		throw new Error('invalid ISO 8601 date');
	}

	return new Date(Date.parse(input));
};

export const convertDateToInt = (date: Date): number => parseInt(moment(date).clone().format('YYYYMMDD'), 10);
export const convertIntToDate = (intValue: number): Date => moment(intValue, 'YYYYMMDD').clone().toDate();
const diffBetweenDays = (start: string | number | Date, end: string | number | Date): number =>
	moment(new Date(start)).clone().diff(new Date(end), 'days');
export const diffBetweenDaysInclusive = (start: string | number | Date, end: string | number | Date): number =>
	diffBetweenDays(start, end) + 1;

export const getTotalOfWeekItems = <T extends Record<string, number>>(weekItems: T[], property: keyof T): number =>
	weekItems.reduce((acc, item) => {
		acc += item[property];
		return acc;
	}, 0);

View on GitHub (pinned to b2c16d5842)