[CAL-3982] Booking limits are being calculated from the attendee's (booker's) perspective (timezone).
Issue Summary
Steps to Reproduce
- Create event with a booking limit
- Book a event from the host's tz ( for simplicity sake)
- Change the attendee tz which overlaps the host's tz but doesn't include the booking we created (day finishes before the event).
Actual Results
https://github.com/calcom/cal.com/assets/86384652/09b1461b-31fe-4057-9dd8-4a5a811ff0a5
Expected Results
Limits should be calculated respective to the host timezone.
Technical details
- Firefox 126
- Node 20
- Fedora 40
@MehulZR Hello there! I'm here to help you with any bugs, questions, or becoming a contributor. Let me know how I can assist you!
To ensure that booking limits are calculated based on the host's timezone rather than the attendee's timezone, you need to set the timeZone parameter to the host's timezone when calling the getSchedule function.
Here is an example from the provided test cases where the timezone is set to IST (Indian Standard Time) for the host:
const scheduleForEvent = await getSchedule({
input: {
eventTypeId: 1,
eventTypeSlug: "",
usernameList: [],
// Because this time is in GMT, it will be 00:00 in IST with todayDateString
startTime: `${yesterdayDateString}T18:30:00.000Z`,
endTime: `${plus5DateString}T18:29:59.999Z`,
timeZone: Timezones["+5:30"], // Host's timezone
isTeamEvent: false,
},
});
In this example, the timeZone parameter is set to Timezones["+5:30"], which corresponds to IST. This ensures that the booking limits are calculated based on the host's timezone.
Additionally, the checkBookingLimits function from the @calcom/lib/server/checkBookingLimits.ts module can be used to ensure that all date and time calculations are correctly aligned with the host's local time. This function takes a timeZone parameter, which should be set to the host's timezone. Here is a relevant snippet:
export async function checkBookingLimits(
bookingLimits: IntervalLimit,
eventStartDate: Date,
eventId: number,
rescheduleUid?: string | undefined,
timeZone?: string | null
) {
const parsedBookingLimits = parseBookingLimit(bookingLimits);
if (!parsedBookingLimits) return false;
const limitCalculations = ascendingLimitKeys.map((key) =>
checkBookingLimit({
key,
limitingNumber: parsedBookingLimits[key],
eventStartDate,
eventId,
timeZone,
rescheduleUid,
})
);
try {
return !!(await Promise.all(limitCalculations));
} catch (error) {
throw new HttpError({ message: getErrorFromUnknown(error).message, statusCode: 401 });
}
}
export async function checkBookingLimit({
eventStartDate,
eventId,
key,
limitingNumber,
rescheduleUid,
timeZone,
}: {
eventStartDate: Date;
eventId: number;
key: keyof IntervalLimit;
limitingNumber: number | undefined;
rescheduleUid?: string | undefined;
timeZone?: string | null;
}) {
const eventDateInOrganizerTz = timeZone ? dayjs(eventStartDate).tz(timeZone) : dayjs(eventStartDate);
if (!limitingNumber) return;
const unit = intervalLimitKeyToUnit(key);
const startDate = dayjs(eventDateInOrganizerTz).startOf(unit).toDate();
const endDate = dayjs(eventDateInOrganizerTz).endOf(unit).toDate();
const bookingsInPeriod = await prisma.booking.count({
where: {
status: BookingStatus.ACCEPTED,
eventTypeId: eventId,
startTime: {
gte: startDate,
},
endTime: {
lte: endDate,
},
uid: {
not: rescheduleUid,
},
},
});
if (bookingsInPeriod < limitingNumber) return;
throw new HttpError({
message: `booking_limit_reached`,
statusCode: 403,
});
}
By using the timeZone parameter in both the getSchedule and checkBookingLimits functions, you can ensure that all booking limits are calculated based on the host's timezone [1][2][3].
To continue talking to Dosu, mention @dosu.
Hey @MehulZR , I tried reproducing the bug . Looks like it shows the availablity (based on atendee's time zone) but does not let you book the meeting if limit is already reached (in host's timezone). Correct me if am wrong.
It might be because while creating the booking, it checks from the host's timezone (I haven't checked it).
I tried fixing it but the dayjs lib was behaving weirdly for me.