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

  1. Send `end` as a full ISO 8601 UTC string via new Date().toISOString().
  2. Client-side validate: DateTime.fromISO(end, {zone:'utc'}).isValid === true.
  3. Make sure end is strictly after start before firing the request.
  4. 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

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


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/8ccfa23866c62d0f. Report an issue: GitHub.