calcom/cal.diy · error · BadRequestException

Could not adjust timezone for slot end time ${slot.time} wit

Error message

Could not adjust timezone for slot end time ${slot.time} with timezone ${timeZone}

What it means

A NestJS BadRequestException (HTTP 400) from SlotsOutputService_2024_09_04.getAvailableRangeSlots — the end-time conversion branch. The slot end (slot.time + slotDuration minutes) converted to the target timezone returned null from toISO(). Distinguished from 165 because the start conversion succeeded but the end (after plus({minutes: slotDuration})) failed.

Source

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

    >((acc, [date, slots]) => {
      const availableTimeSlots = slots.filter((slot) => !slot.away);
      if (availableTimeSlots.length > 0) {
        acc[date] = availableTimeSlots.map((slot) => {
          if (timeZone) {
            const start = DateTime.fromISO(slot.time, { zone: "utc" }).setZone(timeZone).toISO();
            if (!start) {
              throw new BadRequestException(
                `Could not adjust timezone for slot ${slot.time} with timezone ${timeZone}`
              );
            }

            const end = DateTime.fromISO(slot.time, { zone: "utc" })
              .plus({ minutes: slotDuration })
              .setZone(timeZone)
              .toISO();

            if (!end) {
              throw new BadRequestException(
                `Could not adjust timezone for slot end time ${slot.time} with timezone ${timeZone}`
              );
            }

            if (!eventType?.seatsPerTimeSlot) {
              return this.getAvailableRangeSlot(start, end);
            }
            return this.getAvailableRangeSlotSeated(
              start,
              end,
              slot.attendees || 0,
              eventType.seatsPerTimeSlot ?? undefined,
              slot.bookingUid
            );
          } else {
            const start = DateTime.fromISO(slot.time, { zone: "utc" }).toISO();
            const end = DateTime.fromISO(slot.time, { zone: "utc" }).plus({ minutes: slotDuration }).toISO();

View on GitHub (pinned to 176037d0af)

Solutions

  1. Always pass an explicit duration query param for range requests when the event type length may be null.
  2. Validate slotDuration is a finite positive integer before relying on it.
  3. Validate timeZone as in 164/165.
  4. If the event type is non-variable, ensure eventType.length is configured.

Example fix

// before
fetch(`/v2/slots?format=range&eventTypeSlug=...&timeZone=${tz}`) // no duration, ET length null

// after
fetch(`/v2/slots?format=range&eventTypeSlug=...&duration=30&timeZone=${encodeURIComponent(tz)}`);
Defensive patterns

Strategy: validation

Validate before calling

import { DateTime } from 'luxon';

function assertRangeParams(duration: unknown, tz: unknown) {
  if (duration !== undefined) {
    const d = Number(duration);
    if (!Number.isFinite(d) || d <= 0) throw new RangeError('duration must be a positive number of minutes');
  }
  if (tz !== undefined && (typeof tz !== 'string' || !DateTime.now().setZone(tz).isValid)) {
    throw new RangeError(`Unsupported timeZone: ${tz}`);
  }
}
assertRangeParams(input.duration, input.timeZone);

Type guard

function isPositiveFiniteMinutes(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && v > 0;
}

Try / catch

try {
  await cal.slots.list({ format: 'range', duration, ..., timeZone });
} catch (e) {
  if (e instanceof HttpError && e.statusCode === 400 && /end time|timezone/i.test(e.message)) {
    // retry with explicit duration or without timeZone
    return cal.slots.list({ format: 'range', duration: eventType.length, ... /* no tz */ });
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /v2/slots with format=range and a timeZone where the start converts but the computed end crosses into an invalid instant — in practice this fires when slotDuration is undefined/NaN producing an invalid DateTime, or when slot.time itself is borderline and the addition overflows luxon's representable range.

Common situations: Event type has no length and no duration passed (slotDuration = undefined → plus undefined minutes → invalid); duration query param coerced to NaN; extreme durations; DST gap near the slot combined with an unusual zone.

Related errors


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