mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-01-14 00:27:55 +00:00
Docs: Update benchmark results
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
const findAvailableSlots = async (cal1, cal2, constraints) => {
|
||||
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/+esm'),
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/customParseFormat.js/+esm'),
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/isBetween.js/+esm')
|
||||
]);
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(isBetween);
|
||||
|
||||
const { durationMinutes, searchRange, workHours } = constraints;
|
||||
const [whStart, whEnd] = [workHours.start.split(':'), workHours.end.split(':')];
|
||||
|
||||
const allBusy = [...cal1, ...cal2]
|
||||
.map(({ start, end }) => ({ start: dayjs(start), end: dayjs(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 {
|
||||
acc[acc.length - 1].end = dayjs.max(acc[acc.length - 1].end, curr.end);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const slots = [];
|
||||
let current = dayjs(searchRange.start);
|
||||
const searchEnd = dayjs(searchRange.end);
|
||||
|
||||
while (current < searchEnd) {
|
||||
const dayStart = current.hour(+whStart[0]).minute(+whStart[1]).second(0);
|
||||
const dayEnd = current.hour(+whEnd[0]).minute(+whEnd[1]).second(0);
|
||||
|
||||
let pointer = dayStart > current ? dayStart : current;
|
||||
|
||||
for (const busy of merged) {
|
||||
if (busy.start >= dayEnd) break;
|
||||
if (busy.end <= pointer) continue;
|
||||
|
||||
while (pointer.add(durationMinutes, 'minute') <= dayjs.min(busy.start, dayEnd)) {
|
||||
const slotEnd = pointer.add(durationMinutes, 'minute');
|
||||
if (pointer >= dayStart && slotEnd <= dayEnd && pointer >= dayjs(searchRange.start)) {
|
||||
slots.push({
|
||||
start: pointer.toISOString(),
|
||||
end: slotEnd.toISOString()
|
||||
});
|
||||
}
|
||||
pointer = pointer.add(durationMinutes, 'minute');
|
||||
}
|
||||
pointer = dayjs.max(pointer, busy.end);
|
||||
}
|
||||
|
||||
while (pointer.add(durationMinutes, 'minute') <= dayEnd) {
|
||||
const slotEnd = pointer.add(durationMinutes, 'minute');
|
||||
if (pointer >= dayStart && slotEnd <= searchEnd) {
|
||||
slots.push({
|
||||
start: pointer.toISOString(),
|
||||
end: slotEnd.toISOString()
|
||||
});
|
||||
}
|
||||
pointer = pointer.add(durationMinutes, 'minute');
|
||||
}
|
||||
|
||||
current = current.add(1, 'day').startOf('day');
|
||||
}
|
||||
|
||||
return slots;
|
||||
};
|
||||
export default findAvailableSlots;
|
||||
@@ -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;
|
||||
@@ -1,78 +0,0 @@
|
||||
async function findAvailableSlots(cal1, cal2, constraints) {
|
||||
const { default: dayjs } = await import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/+esm');
|
||||
const [{ default: utc }, { default: isBetween }, { default: customParseFormat }] = await Promise.all([
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/esm/plugin/utc/+esm'),
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/esm/plugin/isBetween/+esm'),
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/esm/plugin/customParseFormat/+esm')
|
||||
]);
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
const { durationMinutes, searchRange, workHours } = constraints;
|
||||
const allBusy = [...cal1, ...cal2]
|
||||
.map(s => ({ start: dayjs(s.start), end: dayjs(s.end) }))
|
||||
.sort((a, b) => a.start.valueOf() - b.start.valueOf());
|
||||
|
||||
const merged = allBusy.reduce((acc, curr) => {
|
||||
if (!acc.length || acc[acc.length - 1].end.isBefore(curr.start)) {
|
||||
acc.push(curr);
|
||||
} else {
|
||||
acc[acc.length - 1].end = dayjs.max(acc[acc.length - 1].end, curr.end);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const slots = [];
|
||||
const rangeStart = dayjs(searchRange.start);
|
||||
const rangeEnd = dayjs(searchRange.end);
|
||||
const [workStart, workEnd] = [workHours.start, workHours.end];
|
||||
|
||||
const getWorkBounds = (date) => {
|
||||
const [sh, sm] = workStart.split(':').map(Number);
|
||||
const [eh, em] = workEnd.split(':').map(Number);
|
||||
return {
|
||||
start: date.hour(sh).minute(sm).second(0).millisecond(0),
|
||||
end: date.hour(eh).minute(em).second(0).millisecond(0)
|
||||
};
|
||||
};
|
||||
|
||||
let currentDay = rangeStart.startOf('day');
|
||||
while (currentDay.isBefore(rangeEnd) || currentDay.isSame(rangeEnd, 'day')) {
|
||||
const { start: dayWorkStart, end: dayWorkEnd } = getWorkBounds(currentDay);
|
||||
const dayStart = dayjs.max(dayWorkStart, rangeStart);
|
||||
const dayEnd = dayjs.min(dayWorkEnd, rangeEnd);
|
||||
|
||||
if (dayStart.isBefore(dayEnd)) {
|
||||
let cursor = dayStart;
|
||||
|
||||
for (const busy of merged) {
|
||||
if (busy.end.isBefore(dayStart) || busy.start.isAfter(dayEnd)) continue;
|
||||
|
||||
const gapEnd = dayjs.min(busy.start, dayEnd);
|
||||
while (cursor.add(durationMinutes, 'minute').isSameOrBefore(gapEnd)) {
|
||||
slots.push({
|
||||
start: cursor.toISOString(),
|
||||
end: cursor.add(durationMinutes, 'minute').toISOString()
|
||||
});
|
||||
cursor = cursor.add(durationMinutes, 'minute');
|
||||
}
|
||||
cursor = dayjs.max(cursor, busy.end);
|
||||
}
|
||||
|
||||
while (cursor.add(durationMinutes, 'minute').isSameOrBefore(dayEnd)) {
|
||||
slots.push({
|
||||
start: cursor.toISOString(),
|
||||
end: cursor.add(durationMinutes, 'minute').toISOString()
|
||||
});
|
||||
cursor = cursor.add(durationMinutes, 'minute');
|
||||
}
|
||||
}
|
||||
|
||||
currentDay = currentDay.add(1, 'day');
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
@@ -1,68 +0,0 @@
|
||||
async function findAvailableSlots(calendar1, calendar2, constraints) {
|
||||
const [dayjsModule, durationModule] = await Promise.all([
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js'),
|
||||
import('https://cdn.jsdelivr.net/npm/dayjs@1/plugin/duration.js')
|
||||
]);
|
||||
const dayjs = dayjsModule.default;
|
||||
dayjs.extend(durationModule.default);
|
||||
|
||||
const { durationMinutes: duration, searchRange, workHours } = constraints;
|
||||
const searchStart = dayjs(searchRange.start);
|
||||
const searchEnd = dayjs(searchRange.end);
|
||||
const [workStartH, workStartM] = workHours.start.split(':').map(Number);
|
||||
const [workEndH, workEndM] = workHours.end.split(':').map(Number);
|
||||
|
||||
const toDayjs = ({ start, end }) => ({ start: dayjs(start), end: dayjs(end) });
|
||||
const allBusy = [...calendar1, ...calendar2].map(toDayjs);
|
||||
|
||||
for (let day = searchStart.clone().startOf('day'); day.isBefore(searchEnd); day = day.add(1, 'day')) {
|
||||
allBusy.push({
|
||||
start: day,
|
||||
end: day.hour(workStartH).minute(workStartM)
|
||||
});
|
||||
allBusy.push({
|
||||
start: day.hour(workEndH).minute(workEndM),
|
||||
end: day.endOf('day')
|
||||
});
|
||||
}
|
||||
|
||||
const mergedBusy = allBusy
|
||||
.sort((a, b) => a.start - b.start)
|
||||
.reduce((acc, slot) => {
|
||||
const last = acc.at(-1);
|
||||
if (last && slot.start <= last.end) {
|
||||
if (slot.end > last.end) last.end = slot.end;
|
||||
} else {
|
||||
acc.push({ ...slot });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const availableSlots = [];
|
||||
let nextFreeStart = searchStart;
|
||||
|
||||
const findSlotsInGap = (start, end) => {
|
||||
let slotStart = start;
|
||||
while (slotStart.add(duration, 'minute') <= end) {
|
||||
const slotEnd = slotStart.add(duration, 'minute');
|
||||
availableSlots.push({ start: slotStart.toISOString(), end: slotEnd.toISOString() });
|
||||
slotStart = slotEnd;
|
||||
}
|
||||
};
|
||||
|
||||
mergedBusy.forEach(busySlot => {
|
||||
if (busySlot.start > nextFreeStart) {
|
||||
findSlotsInGap(nextFreeStart, busySlot.start);
|
||||
}
|
||||
if (busySlot.end > nextFreeStart) {
|
||||
nextFreeStart = busySlot.end;
|
||||
}
|
||||
});
|
||||
|
||||
if (searchEnd > nextFreeStart) {
|
||||
findSlotsInGap(nextFreeStart, searchEnd);
|
||||
}
|
||||
|
||||
return availableSlots;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
69
tests/7_scheduler/outputs/google_gemini-2.5-pro.js
Normal file
69
tests/7_scheduler/outputs/google_gemini-2.5-pro.js
Normal file
@@ -0,0 +1,69 @@
|
||||
async function findAvailableSlots(cal1, cal2, c) {
|
||||
const {
|
||||
parseISO, formatISO, addMinutes, isAfter, max, startOfDay,
|
||||
setHours, setMinutes, setSeconds, setMilliseconds, eachDayOfInterval
|
||||
} = await import('https://cdn.jsdelivr.net/npm/date-fns@3.6.0/index.mjs');
|
||||
|
||||
const { durationMinutes: dur, searchRange: sr, workHours: wh } = c;
|
||||
const sStart = parseISO(sr.start);
|
||||
const sEnd = parseISO(sr.end);
|
||||
const toDate = s => parseISO(s);
|
||||
|
||||
const [whsH, whsM] = wh.start.split(':').map(Number);
|
||||
const [wheH, wheM] = wh.end.split(':').map(Number);
|
||||
|
||||
const applyTime = (d, h, m) =>
|
||||
setMilliseconds(setSeconds(setMinutes(setHours(startOfDay(d), h), m), 0), 0);
|
||||
|
||||
const busy = [...cal1, ...cal2].map(({ start, end }) => ({
|
||||
start: toDate(start),
|
||||
end: toDate(end),
|
||||
}));
|
||||
|
||||
const nonWork = [];
|
||||
if (sStart < sEnd) {
|
||||
for (const day of eachDayOfInterval({ start: sStart, end: sEnd })) {
|
||||
nonWork.push({
|
||||
start: startOfDay(day),
|
||||
end: applyTime(day, whsH, whsM)
|
||||
});
|
||||
nonWork.push({
|
||||
start: applyTime(day, wheH, wheM),
|
||||
end: addMinutes(startOfDay(day), 1440)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const unavailable = [...busy, ...nonWork].sort((a, b) => a.start - b.start);
|
||||
|
||||
const merged = unavailable.reduce((acc, slot) => {
|
||||
const last = acc[acc.length - 1];
|
||||
if (last && slot.start <= last.end) {
|
||||
last.end = max(last.end, slot.end);
|
||||
} else {
|
||||
acc.push({ ...slot });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const results = [];
|
||||
let ptr = sStart;
|
||||
const allBlocks = [...merged, { start: sEnd, end: sEnd }];
|
||||
|
||||
for (const block of allBlocks) {
|
||||
let slotStart = ptr;
|
||||
while (true) {
|
||||
const slotEnd = addMinutes(slotStart, dur);
|
||||
if (isAfter(slotEnd, block.start)) break;
|
||||
results.push({ start: slotStart, end: slotEnd });
|
||||
slotStart = slotEnd;
|
||||
}
|
||||
ptr = max(ptr, block.end);
|
||||
}
|
||||
|
||||
return results.map(({ start, end }) => ({
|
||||
start: formatISO(start),
|
||||
end: formatISO(end),
|
||||
}));
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
@@ -1,40 +0,0 @@
|
||||
let cache
|
||||
const load=()=>cache||(cache=import('https://cdn.skypack.dev/date-fns@2.30.0?min'))
|
||||
async function findAvailableSlots(calA,calB,rules){
|
||||
const {parseISO:iso,addMinutes:plus,differenceInMinutes:gap,eachDayOfInterval:days,set:setTime,max:maxDate,min:minDate}=await load()
|
||||
const parseCal=c=>c.map(({start,end})=>({start:iso(start),end:iso(end)}))
|
||||
const [sh,sm]=rules.workHours.start.split(':').map(Number)
|
||||
const [eh,em]=rules.workHours.end.split(':').map(Number)
|
||||
const rangeStart=iso(rules.searchRange.start),rangeEnd=iso(rules.searchRange.end)
|
||||
const entries=[...parseCal(calA),...parseCal(calB)].filter(b=>b.end>rangeStart&&b.start<rangeEnd).sort((a,b)=>a.start-b.start)
|
||||
const slots=[]
|
||||
const addFree=(s,e)=>{
|
||||
let cursor=s
|
||||
while(gap(e,cursor)>=rules.durationMinutes){
|
||||
const stop=plus(cursor,rules.durationMinutes)
|
||||
slots.push({start:cursor.toISOString(),end:stop.toISOString()})
|
||||
cursor=stop
|
||||
}
|
||||
}
|
||||
for(const day of days({start:rangeStart,end:rangeEnd})){
|
||||
const workStart=setTime(day,{hours:sh,minutes:sm,seconds:0,milliseconds:0})
|
||||
const workEnd=setTime(day,{hours:eh,minutes:em,seconds:0,milliseconds:0})
|
||||
let spanStart=maxDate([workStart,rangeStart]),spanEnd=minDate([workEnd,rangeEnd])
|
||||
if(spanStart>=spanEnd)continue
|
||||
const busy=entries.filter(b=>b.end>spanStart&&b.start<spanEnd).map(b=>({start:maxDate([b.start,spanStart]),end:minDate([b.end,spanEnd])})).sort((a,b)=>a.start-b.start)
|
||||
const merged=[]
|
||||
for(const block of busy){
|
||||
const last=merged[merged.length-1]
|
||||
if(!last||block.start>last.end)merged.push({start:block.start,end:block.end})
|
||||
else if(block.end>last.end)last.end=block.end
|
||||
}
|
||||
let cursor=spanStart
|
||||
for(const block of merged){
|
||||
if(block.start>cursor)addFree(cursor,block.start)
|
||||
if(block.end>cursor)cursor=block.end
|
||||
}
|
||||
if(cursor<spanEnd)addFree(cursor,spanEnd)
|
||||
}
|
||||
return slots
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
59
tests/7_scheduler/outputs/openai_gpt-5.1-codex.js
Normal file
59
tests/7_scheduler/outputs/openai_gpt-5.1-codex.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const findAvailableSlots = async (a, b, o) => {
|
||||
const { parseISO, formatISO, addDays, startOfDay } = await import('https://cdn.skypack.dev/date-fns@2.30.0');
|
||||
const dur = o.durationMinutes * 6e4;
|
||||
const rng = [parseISO(o.searchRange.start), parseISO(o.searchRange.end)];
|
||||
const split = t => t.split(':').map(Number);
|
||||
const hrs = { s: split(o.workHours.start), e: split(o.workHours.end) };
|
||||
const busy = [...a, ...b]
|
||||
.map(v => ({ start: parseISO(v.start), end: parseISO(v.end) }))
|
||||
.map(v => {
|
||||
const s = new Date(Math.max(v.start, rng[0]));
|
||||
const e = new Date(Math.min(v.end, rng[1]));
|
||||
return s < e ? { start: s, end: e } : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((x, y) => x.start - y.start)
|
||||
.reduce((m, v) => {
|
||||
const last = m[m.length - 1];
|
||||
if (!last || v.start > last.end) m.push({ start: new Date(v.start), end: new Date(v.end) });
|
||||
else if (v.end > last.end) last.end = new Date(v.end);
|
||||
return m;
|
||||
}, []);
|
||||
const slots = [];
|
||||
const collect = (s, e) => {
|
||||
let a = s.getTime(), b = e.getTime();
|
||||
while (a + dur <= b) {
|
||||
const r = a + dur;
|
||||
slots.push({ start: formatISO(new Date(a)), end: formatISO(new Date(r)) });
|
||||
a = r;
|
||||
}
|
||||
};
|
||||
let idx = 0;
|
||||
for (let d = startOfDay(rng[0]); d <= rng[1]; d = addDays(d, 1)) {
|
||||
const ws = new Date(d);
|
||||
ws.setHours(hrs.s[0], hrs.s[1], 0, 0);
|
||||
const we = new Date(d);
|
||||
we.setHours(hrs.e[0], hrs.e[1], 0, 0);
|
||||
const winStart = new Date(Math.max(ws, rng[0]));
|
||||
const winEnd = new Date(Math.min(we, rng[1]));
|
||||
if (winStart >= winEnd) continue;
|
||||
let cursor = winStart;
|
||||
while (idx < busy.length && busy[idx].end <= winStart) idx++;
|
||||
let view = idx;
|
||||
let done = false;
|
||||
while (view < busy.length && busy[view].start < winEnd) {
|
||||
const gapEnd = new Date(Math.min(busy[view].start, winEnd));
|
||||
if (gapEnd > cursor) collect(cursor, gapEnd);
|
||||
cursor = new Date(Math.max(cursor, busy[view].end));
|
||||
if (cursor >= winEnd) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
view++;
|
||||
}
|
||||
if (!done && cursor < winEnd) collect(cursor, winEnd);
|
||||
idx = view;
|
||||
}
|
||||
return slots;
|
||||
};
|
||||
export default findAvailableSlots;
|
||||
@@ -1,75 +0,0 @@
|
||||
async function findAvailableSlots(cal1, cal2, c) {
|
||||
const { DateTime, Interval } = await import('https://cdn.skypack.dev/luxon')
|
||||
const d = c.durationMinutes
|
||||
const sR = c.searchRange
|
||||
const wH = c.workHours
|
||||
const z = 'UTC'
|
||||
const p = (x, y) => DateTime.fromISO(x, { zone: y })
|
||||
const b = c => c.map(v => Interval.fromDateTimes(p(v.start, z), p(v.end, z)))
|
||||
const m = (a, b) => [...a, ...b].sort((x, y) => x.start - y.start)
|
||||
const n = i => {
|
||||
const r = []
|
||||
let c = i[0]
|
||||
for (let k = 1; k < i.length; k++) {
|
||||
const v = i[k]
|
||||
if (c.overlaps(v) || c.abutsStart(v)) c = Interval.fromDateTimes(c.start, c.end > v.end ? c.end : v.end)
|
||||
else r.push(c), (c = v)
|
||||
}
|
||||
r.push(c)
|
||||
return r
|
||||
}
|
||||
const iA = b(cal1)
|
||||
const iB = b(cal2)
|
||||
const all = m(iA, iB)
|
||||
const busy = all.length ? n(all) : []
|
||||
const srI = Interval.fromDateTimes(p(sR.start, z), p(sR.end, z))
|
||||
const whS = wH.start.split(':').map(Number)
|
||||
const whE = wH.end.split(':').map(Number)
|
||||
|
||||
const dayI = []
|
||||
let cur = srI.start.startOf('day')
|
||||
const endDay = srI.end.startOf('day')
|
||||
while (cur <= endDay) {
|
||||
const ws = cur.set({ hour: whS[0], minute: whS[1] })
|
||||
const we = cur.set({ hour: whE[0], minute: whE[1] })
|
||||
const w = Interval.fromDateTimes(ws, we)
|
||||
const clipped = srI.intersection(w)
|
||||
if (clipped && clipped.isValid && clipped.length('minutes') >= d) dayI.push(clipped)
|
||||
cur = cur.plus({ days: 1 })
|
||||
}
|
||||
|
||||
const inv = []
|
||||
let pS = null
|
||||
for (const x of busy) {
|
||||
const xs = x.start < srI.start ? srI.start : x.start
|
||||
const xe = x.end > srI.end ? srI.end : x.end
|
||||
if (!pS) {
|
||||
if (xs > srI.start) inv.push(Interval.fromDateTimes(srI.start, xs))
|
||||
pS = xe
|
||||
} else {
|
||||
if (xs > pS) inv.push(Interval.fromDateTimes(pS, xs))
|
||||
if (xe > pS) pS = xe
|
||||
}
|
||||
}
|
||||
if (!busy.length) inv.push(srI)
|
||||
else if (pS < srI.end) inv.push(Interval.fromDateTimes(pS, srI.end))
|
||||
|
||||
const freeWithinRange = inv
|
||||
.map(v => dayI.map(dv => dv.intersection(v)).filter(x => x && x.isValid))
|
||||
.flat()
|
||||
.filter(x => x.length('minutes') >= d)
|
||||
|
||||
const res = []
|
||||
for (const f of freeWithinRange) {
|
||||
let st = f.start
|
||||
const mins = f.length('minutes')
|
||||
const steps = Math.floor(mins / d)
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const en = st.plus({ minutes: d })
|
||||
res.push({ start: st.toISO(), end: en.toISO() })
|
||||
st = en
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
Reference in New Issue
Block a user