calcom/cal.diy · error · BadRequestException
Invalid end date
Error message
Invalid end date
What it means
A NestJS BadRequestException (HTTP 400) from SlotsInputService_2024_09_04.adjustEndTime. Symmetric to the start-time guard: DateTime.fromISO(endTime, {zone:'utc'}).toISO() returned null, so the `end` query parameter is not a valid ISO datetime. The window end cannot be established.
Source
Thrown at apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts:148
}
const ISOStartTime = dateTime.toISO();
if (ISOStartTime === null) {
throw new BadRequestException("Invalid start date");
}
return ISOStartTime;
}
private adjustEndTime(endTime: string) {
let dateTime = DateTime.fromISO(endTime, { zone: "utc" });
if (dateTime.hour === 0 && dateTime.minute === 0 && dateTime.second === 0) {
dateTime = dateTime.set({ hour: 23, minute: 59, second: 59 });
}
const ISOEndTime = dateTime.toISO();
if (ISOEndTime === null) {
throw new BadRequestException("Invalid end date");
}
return ISOEndTime;
}
}
View on GitHub (pinned to 176037d0af)
Solutions
- Send `end` as a full ISO 8601 UTC string via new Date().toISOString().
- Client-side validate: DateTime.fromISO(end, {zone:'utc'}).isValid === true.
- Make sure end is strictly after start before firing the request.
- Default a missing end to start + N days explicitly rather than passing an empty string.
Example fix
// before
fetch(`/v2/slots?start=${start}&end=${endPicker}`) // '09/04/2024'
// after
const end = DateTime.fromJSDate(endPicker).toUTC().toISO();
if (!end) throw new Error('bad end');
fetch(`/v2/slots?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`); Defensive patterns
Strategy: validation
Validate before calling
import { DateTime } from 'luxon';
function toValidEndISO(end: unknown, startISO: string): string {
if (typeof end !== 'string') throw new TypeError('end must be an ISO string');
const dt = DateTime.fromISO(end, { zone: 'utc' });
if (!dt.isValid) throw new RangeError(`Invalid end: ${dt.invalidReason} (${end})`);
if (dt.toMillis() <= DateTime.fromISO(startISO, { zone: 'utc' }).toMillis()) {
throw new RangeError('end must be after start');
}
return dt.toISO()!;
}
const end = toValidEndISO(input.end, start); Type guard
function isISODateAfter(v: unknown, minISO: string): v is string {
if (typeof v !== 'string') return false;
const dt = DateTime.fromISO(v, { zone: 'utc' });
return dt.isValid && dt.toMillis() > DateTime.fromISO(minISO, { zone: 'utc' }).toMillis();
} Try / catch
try {
await cal.slots.list({ ..., end });
} catch (e) {
if (e instanceof HttpError && e.statusCode === 400 && /end/i.test(e.message)) {
throw new UserFacingError('Please pick a valid end date/time after the start.');
}
throw e;
} Prevention
- Default end to start + N days explicitly rather than leaving it empty.
- Validate end > start before the request.
- Use ISO strings from a Date library, not user-typed formats.
- Encode the value to survive URL transport.
When it happens
Trigger: Calling GET /v2/slots/2024-09-04 with a malformed, empty, or non-ISO `end` parameter, or with `end` earlier than `start` in a way that produces an invalid window here (though start>end is caught later as a time-range error; this one is strictly about unparseable values).
Common situations: Reusing a date picker that returns 'MM/DD/YYYY'; sending a relative string like 'now+7d'; copy-paste introducing a stray space; the end being omitted and serialized as 'undefined'/'null'.
Related errors
- Invalid start date
- Invalid start date
- Invalid end date
- Could not adjust timezone for slot ${slot.time} with timezon
- Could not adjust timezone for slot end time ${slot.time} wit
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/8ccfa23866c62d0f.
Report an issue: GitHub.