calcom/cal.diy · error · BadRequestException

Invalid Date.

Error message

Invalid Date.

What it means

Thrown inside the catch block of isDateString() in the OOO (out-of-office) DTO transformer. isDateString() runs a strict ISO-8601 UTC regex against the input; the try/catch is a defensive guard around RegExp.prototype.test(). In practice RegExp.test() coerces its argument to a string and does not throw for normal values, so this branch is effectively unreachable for well-typed string inputs and only fires if a non-stringable value (e.g. a Symbol or a getter that throws) reaches the regex at runtime despite the TypeScript signature.

Source

Thrown at apps/api/v2/src/modules/ooo/inputs/ooo.input.ts:23

import { SkipTakePagination } from "@calcom/platform-types";

export enum OutOfOfficeReason {
  UNSPECIFIED = "unspecified",
  VACATION = "vacation",
  TRAVEL = "travel",
  SICK_LEAVE = "sick",
  PUBLIC_HOLIDAY = "public_holiday",
}

export type OutOfOfficeReasonType = `${OutOfOfficeReason}`;

const isDateString = (dateString: string) => {
  try {
    const isoDateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{3})?Z$/;
    return isoDateRegex.test(dateString);
  } catch {
    throw new BadRequestException("Invalid Date.");
  }
};

export class CreateOutOfOfficeEntryDto {
  @Transform(({ value }: { value: string }) => {
    if (isDateString(value)) {
      const date = new Date(value);
      date.setUTCHours(0, 0, 0, 0);
      return date;
    }
    throw new BadRequestException("Invalid Date.");
  })
  @IsDate()
  @ApiProperty({
    description: "The start date and time of the out of office period in ISO 8601 format in UTC timezone.",
    example: "2023-05-01T00:00:00.000Z",
  })
  start!: Date;

View on GitHub (pinned to 176037d0af)

Solutions

  1. Confirm the request body sends start/end as ISO-8601 strings (e.g. "2023-05-01T00:00:00.000Z"), never as Date objects or other primitives.
  2. If you maintain this code, make isDateString() return false instead of throwing on a failed test — the catch is dead for strings and the line-34 path already handles the negative case.
  3. Add a class-validator @IsString() / @Matches() guard before @Transform so non-string values are rejected earlier with a clearer message.

Example fix

// before
const isDateString = (dateString: string) => {
  try {
    const isoDateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{3})?Z$/;
    return isoDateRegex.test(dateString);
  } catch {
    throw new BadRequestException("Invalid Date.");
  }
};
// after (remove the unreachable catch; let the caller throw)
const ISO_DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{3})?Z$/;
const isDateString = (dateString: string): boolean => ISO_DATE_REGEX.test(dateString);
Defensive patterns

Strategy: validation

Validate before calling

const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
const ensureIsoString = (v: unknown): string => {
  if (typeof v !== 'string' || !ISO.test(v))
    throw new Error('start/end must be ISO-8601 UTC string');
  return v;
};

Type guard

const isIsoDateString = (v: unknown): v is string =>
  typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(v);

Prevention

When it happens

Trigger: A POST/PATCH to /v2/ooo (create/update out-of-office entry) where the request body reaches the @Transform pipe with a value whose toString/getter throws during regex.test(). Cannot be triggered by an ordinary malformed date string — those fall through to the line-34 throw instead.

Common situations: A client serializing a Date object, BigInt, or Symbol into the start/end JSON field; a class-transformer / class-validator pipeline misconfigured to pass non-primitives; a custom serialization layer that injects objects with throwing toJSON() hooks.

Related errors


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