-
Notifications
You must be signed in to change notification settings - Fork 0
Automated Test: date-algorithm-enhanced #366
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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+106
to
+131
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. 🛠️ Refactor suggestion | 🟠 Major Refactor: Static analysis correctly flags that this callback doesn't return a value on all paths. When the date matches but the slot is within the override bounds, Consider refactoring to use ♻️ Suggested refactor for clarity- 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").isSame(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;
- }
- }
- })
- ) {
+ const slotOutsideOverride = dateOverrides.some((date) => {
+ const utcOffset = organizerTimeZone ? dayjs.tz(date.start, organizerTimeZone).utcOffset() * -1 : 0;
+ const overrideStart = dayjs(date.start).add(utcOffset, "minutes");
+ const overrideEnd = dayjs(date.end).add(utcOffset, "minutes");
+
+ if (overrideStart.format("YYYY MM DD") !== slotStartTime.format("YYYY MM DD")) {
+ return false; // Not the same day
+ }
+
+ dateOverrideExist = true;
+
+ // Check if slot is outside the override window
+ if (overrideStart.isSame(overrideEnd)) {
+ return true; // Zero-length override blocks the day
+ }
+ if (slotEndTime.isBefore(overrideStart) || slotEndTime.isSame(overrideStart)) {
+ return true;
+ }
+ if (slotStartTime.isAfter(overrideEnd)) {
+ return true;
+ }
+ return false; // Slot is within override
+ });
+
+ if (slotOutsideOverride) {
// slot is not within the date override
return false;
}🧰 Tools🪛 Biome (2.3.13)[error] 106-106: This callback passed to find() iterable method should always return a value. Add missing return statements so that this callback returns a value on all execution paths. (lint/suspicious/useIterableCallbackReturn) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (dateOverrideExist) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+141
to
+145
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: Both Line 142 computes 🐛 Proposed fix 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;
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // slot is outside of working hours | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+138
to
+151
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. 🛠️ Refactor suggestion | 🟠 Major Refactor: Working hours Same issue as above - this callback only returns ♻️ Suggested refactor- if (
- workingHours.find((workingHour) => {
- if (workingHour.days.includes(slotStartTime.day())) {
- const start = slotStartTime.hour() * 60 + slotStartTime.minute();
- const end = slotEndTime.hour() * 60 + slotEndTime.minute();
- if (start < workingHour.startTime || end > workingHour.endTime) {
- return true;
- }
- }
- })
- ) {
+ const slotOutsideWorkingHours = workingHours.some((workingHour) => {
+ if (!workingHour.days.includes(slotStartTime.day())) {
+ return false;
+ }
+ const start = slotStartTime.hour() * 60 + slotStartTime.minute();
+ const end = slotEndTime.hour() * 60 + slotEndTime.minute();
+ return start < workingHour.startTime || end > workingHour.endTime;
+ });
+
+ if (slotOutsideWorkingHours) {
// slot is outside of working hours
return false;
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.3.13)[error] 139-139: This callback passed to find() iterable method should always return a value. Add missing return statements so that this callback returns a value on all execution paths. (lint/suspicious/useIterableCallbackReturn) 🤖 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.
Bug: Dayjs object comparison with
===always returnsfalse.Comparing two Dayjs objects with
===checks reference equality, not value equality. This condition will never be true, even when the start and end times are identical.🐛 Proposed fix using `.isSame()`
📝 Committable suggestion
🤖 Prompt for AI Agents