calcom/cal.diy · warning · BadRequestException

Invalid ${key} hours. Expected value between 0 and 23

Error message

Invalid ${key} hours. Expected value between 0 and 23

What it means

Thrown by transformStringToDate when the parsed hours value from the time portion falls outside the valid 0–23 range. The hours component is extracted from the first colon-separated segment of the time portion and converted via Number(). Values like 24, -1, or NaN-producing strings trigger HTTP 400. The ${key} interpolation identifies which field (startTime or endTime) had the invalid hours.

Source

Thrown at apps/api/v2/src/platform/schedules/schedules_2024_04_15/inputs/create-availability.input.ts:46

  const dateTimeParts = value.split("T");
  if (dateTimeParts.length !== 2) {
    throw new BadRequestException(
      `Invalid datestring format. Expected format(ISO8061): 2025-04-12T13:17:56.324Z. Received: ${value}`
    );
  }

  const timePart = dateTimeParts[1].split(".")[0]; // Removes milliseconds
  const parts = timePart.split(":");

  if (parts.length !== 3) {
    throw new BadRequestException(
      `Invalid time format. Expected format(ISO8061): 2025-0412T13:17:56.324Z. Received: ${value}`
    );
  }
  const [hours, minutes, seconds] = parts.map(Number);

  if (hours < 0 || hours > 23) {
    throw new BadRequestException(`Invalid ${key} hours. Expected value between 0 and 23`);
  }

  if (minutes < 0 || minutes > 59) {
    throw new BadRequestException(`Invalid ${key} minutes. Expected value between 0 and 59`);
  }

  if (seconds < 0 || seconds > 59) {
    throw new BadRequestException(`Invalid ${key} seconds. Expected value between 0 and 59`);
  }

  return new Date(new Date().setUTCHours(hours, minutes, seconds, 0));
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Verify the hours value in the time string is between 0 and 23 (24-hour format).
  2. Use a Date object and toISOString() to guarantee valid hour values from the start.
  3. Add a client-side assertion: if (hours < 0 || hours > 23) throw before sending.

Example fix

// before
"startTime": "2025-04-12T24:00:00.000Z"
// after
"startTime": new Date(2025, 3, 12, 0, 0, 0).toISOString()
Defensive patterns

Strategy: validation

Validate before calling

function validateHours(isoString: string, fieldName: string): void {
  const timePart = isoString.split('T')[1]?.split('.')[0];
  const hours = Number(timePart?.split(':')[0]);
  if (isNaN(hours) || hours < 0 || hours > 23) {
    throw new Error(`${fieldName}: hours must be 0-23, got ${hours}`);
  }
}

validateHours(item.startTime, 'startTime');

Type guard

function hasValidHours(isoString: string): boolean {
  const timePart = isoString.split('T')[1]?.split('.')[0];
  if (!timePart) return false;
  const hours = Number(timePart.split(':')[0]);
  return !isNaN(hours) && hours >= 0 && hours <= 23;
}

Prevention

When it happens

Trigger: Sending a time like '24:00:00' (hours = 24); sending '-01:00:00'; sending a non-numeric hours token like 'ab:00:00' which Number() converts to NaN (NaN < 0 is false but NaN > 23 is also false, so NaN may actually slip through — this is a latent bug).

Common situations: Client-side arithmetic produces an hours value ≥ 24 after timezone conversion; 12-hour clock formatting without AM/PM conversion sends hours like 13+ incorrectly; manual time string construction with an off-by-one error.

Related errors


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