-
Notifications
You must be signed in to change notification settings - Fork 0
Advanced date override handling and timezone compatibility improvements #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: date-algorithm-base
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ import type prisma from "@calcom/prisma"; | |
| import { availabilityUserSelect } from "@calcom/prisma"; | ||
| import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; | ||
| import type { EventBusyDate } from "@calcom/types/Calendar"; | ||
| import type { WorkingHours } from "@calcom/types/schedule"; | ||
|
|
||
| import { TRPCError } from "@trpc/server"; | ||
|
|
||
|
|
@@ -75,12 +76,21 @@ const checkIfIsAvailable = ({ | |
| time, | ||
| busy, | ||
| eventLength, | ||
| dateOverrides = [], | ||
| workingHours = [], | ||
| currentSeats, | ||
| organizerTimeZone, | ||
| }: { | ||
| time: Dayjs; | ||
| busy: EventBusyDate[]; | ||
| eventLength: number; | ||
| dateOverrides?: { | ||
| start: Date; | ||
| end: Date; | ||
| }[]; | ||
| workingHours?: WorkingHours[]; | ||
| currentSeats?: CurrentSeats; | ||
| organizerTimeZone?: string; | ||
| }): boolean => { | ||
| if (currentSeats?.some((booking) => booking.startTime.toISOString() === time.toISOString())) { | ||
| return true; | ||
|
|
@@ -89,6 +99,57 @@ const checkIfIsAvailable = ({ | |
| const slotEndTime = time.add(eventLength, "minutes").utc(); | ||
| const slotStartTime = time.utc(); | ||
|
|
||
| //check if date override for slot exists | ||
| let dateOverrideExist = false; | ||
|
|
||
| if ( | ||
| dateOverrides.find((date) => { | ||
| const utcOffset = organizerTimeZone ? dayjs.tz(date.start, organizerTimeZone).utcOffset() * -1 : 0; | ||
|
|
||
| if ( | ||
| dayjs(date.start).add(utcOffset, "minutes").format("YYYY MM DD") === | ||
| slotStartTime.format("YYYY MM DD") | ||
| ) { | ||
| dateOverrideExist = true; | ||
| if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) { | ||
| return true; | ||
| } | ||
| if ( | ||
| slotEndTime.isBefore(dayjs(date.start).add(utcOffset, "minutes")) || | ||
| slotEndTime.isSame(dayjs(date.start).add(utcOffset, "minutes")) | ||
| ) { | ||
| return true; | ||
| } | ||
| if (slotStartTime.isAfter(dayjs(date.end).add(utcOffset, "minutes"))) { | ||
| return true; | ||
| } | ||
| } | ||
| }) | ||
| ) { | ||
| // slot is not within the date override | ||
| return false; | ||
| } | ||
|
|
||
| if (dateOverrideExist) { | ||
| return true; | ||
| } | ||
|
Comment on lines
+102
to
+135
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical bug: Dayjs object comparison uses Line 114 compares two Dayjs objects using Additionally, the 🐛 Proposed fix for the Dayjs comparison- if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
+ if (dayjs(date.start).add(utcOffset, "minutes").isSame(dayjs(date.end).add(utcOffset, "minutes"))) {
return true;
}🤖 Prompt for AI Agents |
||
|
|
||
| //if no date override for slot exists check if it is within normal work hours | ||
| if ( | ||
| workingHours.find((workingHour) => { | ||
| if (workingHour.days.includes(slotStartTime.day())) { | ||
| const start = slotStartTime.hour() * 60 + slotStartTime.minute(); | ||
| const end = slotStartTime.hour() * 60 + slotStartTime.minute(); | ||
| if (start < workingHour.startTime || end > workingHour.endTime) { | ||
| return true; | ||
| } | ||
| } | ||
| }) | ||
| ) { | ||
| // slot is outside of working hours | ||
| return false; | ||
| } | ||
|
Comment on lines
+137
to
+151
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical bug: Line 142 computes 🐛 Proposed fix if (
workingHours.find((workingHour) => {
if (workingHour.days.includes(slotStartTime.day())) {
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
- const end = slotStartTime.hour() * 60 + slotStartTime.minute();
+ const end = slotEndTime.hour() * 60 + slotEndTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}
}
})
) {🤖 Prompt for AI Agents |
||
|
|
||
| return busy.every((busyTime) => { | ||
| const startTime = dayjs.utc(busyTime.start).utc(); | ||
| const endTime = dayjs.utc(busyTime.end); | ||
|
|
@@ -115,7 +176,6 @@ const checkIfIsAvailable = ({ | |
| else if (startTime.isBetween(time, slotEndTime)) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| }); | ||
| }; | ||
|
|
@@ -348,7 +408,11 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx: | |
| ); | ||
| // flattens availability of multiple users | ||
| const dateOverrides = userAvailability.flatMap((availability) => | ||
| availability.dateOverrides.map((override) => ({ userId: availability.user.id, ...override })) | ||
| availability.dateOverrides.map((override) => ({ | ||
| userId: availability.user.id, | ||
| timeZone: availability.timeZone, | ||
| ...override, | ||
| })) | ||
| ); | ||
| const workingHours = getAggregateWorkingHours(userAvailability, eventType.schedulingType); | ||
| const availabilityCheckProps = { | ||
|
|
@@ -372,6 +436,9 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx: | |
|
|
||
| const timeSlots: ReturnType<typeof getTimeSlots> = []; | ||
|
|
||
| const organizerTimeZone = | ||
| eventType.timeZone || eventType?.schedule?.timeZone || userAvailability?.[0]?.timeZone; | ||
|
|
||
| for ( | ||
| let currentCheckedTime = startTime; | ||
| currentCheckedTime.isBefore(endTime); | ||
|
|
@@ -386,8 +453,7 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx: | |
| dateOverrides, | ||
| minimumBookingNotice: eventType.minimumBookingNotice, | ||
| frequency: eventType.slotInterval || input.duration || eventType.length, | ||
| organizerTimeZone: | ||
| eventType.timeZone || eventType?.schedule?.timeZone || userAvailability?.[0]?.timeZone, | ||
| organizerTimeZone, | ||
| }) | ||
| ); | ||
| } | ||
|
|
@@ -423,13 +489,15 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx: | |
| time: slot.time, | ||
| ...schedule, | ||
| ...availabilityCheckProps, | ||
| organizerTimeZone: schedule.timeZone, | ||
| }); | ||
| const endCheckForAvailability = performance.now(); | ||
| checkForAvailabilityCount++; | ||
| checkForAvailabilityTime += endCheckForAvailability - startCheckForAvailability; | ||
| return isAvailable; | ||
| }); | ||
| }); | ||
|
|
||
| // what else are you going to call it? | ||
| const looseHostAvailability = userAvailability.filter(({ user: { isFixed } }) => !isFixed); | ||
| if (looseHostAvailability.length > 0) { | ||
|
|
@@ -446,6 +514,7 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx: | |
| time: slot.time, | ||
| ...userSchedule, | ||
| ...availabilityCheckProps, | ||
| organizerTimeZone: userSchedule.timeZone, | ||
| }); | ||
| }); | ||
| return slot; | ||
|
|
@@ -507,17 +576,19 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx: | |
| return false; | ||
| } | ||
|
|
||
| const userSchedule = userAvailability.find(({ user: { id: userId } }) => userId === slotUserId); | ||
|
|
||
| return checkIfIsAvailable({ | ||
| time: slot.time, | ||
| busy, | ||
| ...availabilityCheckProps, | ||
| organizerTimeZone: userSchedule?.timeZone, | ||
| }); | ||
| }); | ||
| return slot; | ||
| }) | ||
| .filter((slot) => !!slot.userIds?.length); | ||
| } | ||
|
|
||
| availableTimeSlots = availableTimeSlots.filter((slot) => isTimeWithinBounds(slot.time)); | ||
|
|
||
| const computedAvailableSlots = availableTimeSlots.reduce( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 1123
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 440
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 365
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 450
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 150
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 202
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 820
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 728
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 232
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 854
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 140
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 922
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 1382
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 453
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 617
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com
Length of output: 1001
Add validation or fallback for
override.timeZonewhich is optional.The
timeZoneproperty inTimeRange(the type for date overrides) is optional, sooverride.timeZonecan be undefined. Passingundefinedtodayjs().tz()on line 212 will not correctly handle the timezone conversion. Add a fallback toorganizerTimeZonewhentimeZoneis missing:🤖 Prompt for AI Agents