perf: optimize getSlots performance by selectively merging overlapping date ranges (#21370)

* perf: optimize getSlots performance by selectively merging overlapping date ranges

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: handle date ranges with end time before start time in filterRedundantDateRanges

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* perf: apply filterRedundantDateRanges to all scheduling types

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: preserve uniqueness check before filtering redundant date ranges

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: keith@cal.com <keithwillcode@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2025-05-17 03:44:56 +00:00
committed by GitHub
co-authored by keith@cal.com
parent 816df3704e
commit 6a85d0e10b
3 changed files with 87 additions and 2 deletions
+5 -2
View File
@@ -2,6 +2,7 @@ import type { DateRange } from "@calcom/lib/date-ranges";
import { intersect } from "@calcom/lib/date-ranges";
import { SchedulingType } from "@calcom/prisma/enums";
import { filterRedundantDateRanges } from "./getAggregatedAvailability/date-range-utils/filterRedundantDateRanges";
import { mergeOverlappingDateRanges } from "./getAggregatedAvailability/date-range-utils/mergeOverlappingDateRanges";
function uniqueAndSortedDateRanges(ranges: DateRange[]): DateRange[] {
@@ -48,6 +49,8 @@ export const getAggregatedAvailability = (
);
}
const availability = intersect(dateRangesToIntersect);
// we no longer merge overlapping date ranges, rr-hosts need to be individually available here.
return uniqueAndSortedDateRanges(availability);
const uniqueRanges = uniqueAndSortedDateRanges(availability);
return filterRedundantDateRanges(uniqueRanges);
};
@@ -0,0 +1,45 @@
import { describe, it, expect } from "vitest";
import dayjs from "@calcom/dayjs";
import { filterRedundantDateRanges } from "./filterRedundantDateRanges";
describe("filterRedundantDateRanges", () => {
it("should remove date ranges that are completely contained within others", () => {
const dateRanges = [
{ start: dayjs("2025-01-23T11:00:00.000Z"), end: dayjs("2025-01-23T12:00:00.000Z") },
{ start: dayjs("2025-01-23T11:15:00.000Z"), end: dayjs("2025-01-23T11:45:00.000Z") }, // Contained within the first range
{ start: dayjs("2025-01-23T13:00:00.000Z"), end: dayjs("2025-01-23T14:00:00.000Z") },
];
const result = filterRedundantDateRanges(dateRanges);
expect(result.length).toBe(2);
expect(result[0].start.format()).toEqual(dayjs("2025-01-23T11:00:00.000Z").format());
expect(result[0].end.format()).toEqual(dayjs("2025-01-23T12:00:00.000Z").format());
expect(result[1].start.format()).toEqual(dayjs("2025-01-23T13:00:00.000Z").format());
expect(result[1].end.format()).toEqual(dayjs("2025-01-23T14:00:00.000Z").format());
});
it("should not remove overlapping ranges that aren't completely contained", () => {
const dateRanges = [
{ start: dayjs("2025-01-23T11:00:00.000Z"), end: dayjs("2025-01-23T12:00:00.000Z") },
{ start: dayjs("2025-01-23T11:30:00.000Z"), end: dayjs("2025-01-23T12:30:00.000Z") }, // Overlaps but not contained
{ start: dayjs("2025-01-23T13:00:00.000Z"), end: dayjs("2025-01-23T14:00:00.000Z") },
];
const result = filterRedundantDateRanges(dateRanges);
expect(result.length).toBe(3);
});
it("should return an empty array when given an empty array", () => {
expect(filterRedundantDateRanges([])).toEqual([]);
});
it("should return the same array when there's only one range", () => {
const dateRanges = [{ start: dayjs("2025-01-23T11:00:00.000Z"), end: dayjs("2025-01-23T12:00:00.000Z") }];
expect(filterRedundantDateRanges(dateRanges)).toEqual(dateRanges);
});
});
@@ -0,0 +1,37 @@
import type { DateRange } from "@calcom/lib/date-ranges";
/**
* Filters out date ranges that are completely covered by other date ranges.
* Unlike mergeOverlappingDateRanges, this doesn't merge overlapping ranges,
* it only removes ranges that are completely contained within others.
*/
export function filterRedundantDateRanges(dateRanges: DateRange[]): DateRange[] {
if (dateRanges.length <= 1) return dateRanges;
const sortedRanges = [...dateRanges].sort((a, b) => a.start.valueOf() - b.start.valueOf());
return sortedRanges.filter((range, index) => {
if (range.end.valueOf() < range.start.valueOf()) {
return true;
}
for (let i = 0; i < sortedRanges.length; i++) {
if (i === index) continue; // Skip comparing with itself
const otherRange = sortedRanges[i];
if (otherRange.end.valueOf() < otherRange.start.valueOf()) {
continue;
}
if (
otherRange.start.valueOf() <= range.start.valueOf() &&
otherRange.end.valueOf() >= range.end.valueOf()
) {
return false; // This range is redundant, filter it out
}
}
return true; // Keep this range
});
}