Docs: Update benchmark results

This commit is contained in:
github-actions[bot]
2025-11-13 21:24:36 +00:00
parent 63fdb538ff
commit a38ae2d0c5
62 changed files with 532 additions and 1161 deletions

View File

@@ -1,85 +1,108 @@
const findAvailableSlots = async (cal1, cal2, constraints) => {
const { DateTime, Interval } = await import('https://cdn.skypack.dev/luxon');
const { default: dayjs } = await import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/+esm');
const [{ default: utc }, { default: customParseFormat }, { default: isBetween }] = await Promise.all([
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/utc.js'),
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/customParseFormat.js'),
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/isBetween.js')
]);
const { durationMinutes: dur, searchRange: range, workHours: wh } = constraints;
const [rangeStart, rangeEnd] = [DateTime.fromISO(range.start), DateTime.fromISO(range.end)];
const [whStart, whEnd] = wh.start.split(':').map(Number);
const [whStartMin, whEndMin] = [whStart * 60 + (wh.start.split(':')[1] || 0),
whEnd.split(':').map(Number).reduce((h, m) => h * 60 + m)];
const mergeIntervals = (intervals) => {
if (!intervals.length) return [];
const sorted = intervals.sort((a, b) => a.start - b.start);
const merged = [sorted[0]];
for (let i = 1; i < sorted.length; i++) {
const last = merged[merged.length - 1];
if (sorted[i].start <= last.end) {
last.end = last.end > sorted[i].end ? last.end : sorted[i].end;
} else {
merged.push(sorted[i]);
}
}
return merged;
};
const toBusy = (cal) => cal.map(({ start, end }) => ({
start: DateTime.fromISO(start),
end: DateTime.fromISO(end)
}));
const allBusy = mergeIntervals([...toBusy(cal1), ...toBusy(cal2)]);
const isWorkHour = (dt) => {
const min = dt.hour * 60 + dt.minute;
return min >= whStartMin && min < whEndMin;
};
const slots = [];
let cursor = rangeStart;
while (cursor < rangeEnd) {
if (!isWorkHour(cursor)) {
cursor = cursor.plus({ minutes: 1 });
continue;
}
const slotEnd = cursor.plus({ minutes: dur });
if (slotEnd > rangeEnd) break;
let validSlot = true;
let tempCursor = cursor;
while (tempCursor < slotEnd) {
if (!isWorkHour(tempCursor)) {
validSlot = false;
break;
}
tempCursor = tempCursor.plus({ minutes: 1 });
}
if (!validSlot) {
cursor = cursor.plus({ minutes: 1 });
continue;
}
const overlaps = allBusy.some(busy =>
(cursor >= busy.start && cursor < busy.end) ||
(slotEnd > busy.start && slotEnd <= busy.end) ||
(cursor <= busy.start && slotEnd >= busy.end)
);
if (!overlaps) {
slots.push({
start: cursor.toISO(),
end: slotEnd.toISO()
});
cursor = slotEnd;
dayjs.extend(utc);
dayjs.extend(customParseFormat);
dayjs.extend(isBetween);
const { durationMinutes, searchRange, workHours } = constraints;
const allBusy = [...cal1, ...cal2].map(s => ({
start: dayjs(s.start),
end: dayjs(s.end)
})).sort((a, b) => a.start - b.start);
const merged = allBusy.reduce((acc, curr) => {
if (!acc.length || acc[acc.length - 1].end < curr.start) {
acc.push(curr);
} else {
const nextBusy = allBusy.find(b => b.end > cursor && b.start <= cursor);
cursor = nextBusy ? nextBusy.end : cursor.plus({ minutes: 1 });
acc[acc.length - 1].end = dayjs.max(acc[acc.length - 1].end, curr.end);
}
return acc;
}, []);
const slots = [];
const searchStart = dayjs(searchRange.start);
const searchEnd = dayjs(searchRange.end);
const [whStart, whEnd] = [
dayjs(workHours.start, 'HH:mm'),
dayjs(workHours.end, 'HH:mm')
];
const isInWorkHours = (dt) => {
const time = dayjs().hour(dt.hour()).minute(dt.minute());
return time.isBetween(whStart, whEnd, null, '[)');
};
const addSlot = (start, end) => {
let curr = start;
while (curr.add(durationMinutes, 'minute') <= end) {
if (isInWorkHours(curr) && isInWorkHours(curr.add(durationMinutes, 'minute').subtract(1, 'second'))) {
const slotEnd = curr.add(durationMinutes, 'minute');
const dayEnd = curr.hour(whEnd.hour()).minute(whEnd.minute());
if (slotEnd <= dayEnd) {
slots.push({
start: curr.toISOString(),
end: slotEnd.toISOString()
});
}
}
curr = curr.add(15, 'minute');
}
};
let prevEnd = searchStart.hour(whStart.hour()).minute(whStart.minute());
if (prevEnd < searchStart) prevEnd = searchStart;
merged.forEach(busy => {
if (busy.start > prevEnd) {
let gapStart = prevEnd;
let gapEnd = busy.start;
let day = gapStart.startOf('day');
while (day <= gapEnd) {
const dayStart = dayjs.max(
gapStart,
day.hour(whStart.hour()).minute(whStart.minute())
);
const dayEnd = dayjs.min(
gapEnd,
day.hour(whEnd.hour()).minute(whEnd.minute())
);
if (dayStart < dayEnd && dayStart >= searchStart && dayEnd <= searchEnd) {
addSlot(dayStart, dayEnd);
}
day = day.add(1, 'day');
}
}
prevEnd = dayjs.max(prevEnd, busy.end);
});
const finalEnd = searchEnd.hour(whEnd.hour()).minute(whEnd.minute());
if (prevEnd < finalEnd) {
let day = prevEnd.startOf('day');
while (day <= finalEnd) {
const dayStart = dayjs.max(
prevEnd,
day.hour(whStart.hour()).minute(whStart.minute())
);
const dayEnd = dayjs.min(
finalEnd,
day.hour(whEnd.hour()).minute(whEnd.minute())
);
if (dayStart < dayEnd && dayStart >= searchStart && dayEnd <= searchEnd) {
addSlot(dayStart, dayEnd);
}
day = day.add(1, 'day');
}
}
return slots;
};
export default findAvailableSlots;