calcom/cal.diy · error · BadRequestException

Invalid start date

Error message

Invalid start date

What it means

A NestJS BadRequestException (HTTP 400) from SlotsInputService_2024_09_04.adjustStartTime. Luxon's DateTime.fromISO(startTime, {zone:'utc'}).toISO() returned null — the `start` query parameter could not be parsed into a valid ISO datetime. This guards the slot query window start.

Source

Thrown at apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts:134

  }

  private async getEventTypeUser(input: ByUsernameAndEventTypeSlug_2024_09_04) {
    return await this.usersRepository.findByUsername(input.username);
  }

  private async getEventTypeTeam(input: ByTeamSlugAndEventTypeSlug_2024_09_04) {
    return await this.teamsRepository.findTeamBySlug(input.teamSlug);
  }

  private adjustStartTime(startTime: string) {
    let dateTime = DateTime.fromISO(startTime, { zone: "utc" });
    if (dateTime.hour === 0 && dateTime.minute === 0 && dateTime.second === 0) {
      dateTime = dateTime.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
    }

    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 `start` as a full ISO 8601 UTC string, e.g. new Date().toISOString() (YYYY-MM-DDTHH:mm:ss.sssZ).
  2. Validate the string with luxon client-side: DateTime.fromISO(start, {zone:'utc'}).isValid must be true before the request.
  3. URL-encode the value so '+' and ':' survive transport.
  4. Ensure the field is not empty or undefined — the pipe may pass it through but luxon rejects it here.

Example fix

// before
fetch(`/v2/slots?start=${dateOnly}`)  // '2024-09-04' — too short

// after
const start = DateTime.fromISO(dateOnly, { zone: 'utc' }).startOf('day').toISO();
if (!start) throw new Error('bad start');
fetch(`/v2/slots?start=${encodeURIComponent(start!)}`);
Defensive patterns

Strategy: validation

Validate before calling

import { DateTime } from 'luxon';

function toValidStartISO(start: unknown): string {
  if (typeof start !== 'string') throw new TypeError('start must be an ISO string');
  const dt = DateTime.fromISO(start, { zone: 'utc' });
  if (!dt.isValid) throw new RangeError(`Invalid start: ${dt.invalidReason} (${start})`);
  return dt.toISO()!;
}
const start = toValidStartISO(input.start);

Type guard

function isISODateString(v: unknown): v is string {
  if (typeof v !== 'string') return false;
  return DateTime.fromISO(v, { zone: 'utc' }).isValid;
}

Try / catch

try {
  await cal.slots.list({ ..., start });
} catch (e) {
  if (e instanceof HttpError && e.statusCode === 400 && /start/i.test(e.message)) {
    throw new UserFacingError('Please pick a valid start date/time.');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling GET /v2/slots/2024-09-04 with a `start` value that is empty, malformed (e.g. '2024-09-04', 'tomorrow', a unix timestamp as a number), in an unsupported locale format, or contains invalid characters after URL decoding.

Common situations: Passing a bare date 'YYYY-MM-DD' that luxon parses but then toISO returns a value — actually here the issue is fully unparseable strings; passing a JS Date.toString() output with timezone label; client building the string with a broken template literal; forgetting to convert a native Date via toISOString().

Related errors


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