Docs: Update benchmark results

This commit is contained in:
github-actions[bot]
2025-11-13 21:50:29 +00:00
parent 59752cb111
commit f2ef5831a7
31 changed files with 416 additions and 433 deletions

View File

@@ -1,23 +1,22 @@
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'),
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 [{ default: utc }, { 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/isBetween.js/+esm')
]);
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) {
const [whStart, whEnd] = [workHours.start.split(':'), workHours.end.split(':')];
const merged = [...cal1, ...cal2]
.map(s => ({ start: dayjs(s.start), end: dayjs(s.end) }))
.sort((a, b) => a.start - b.start);
const busy = merged.reduce((acc, curr) => {
if (!acc.length || curr.start > acc[acc.length - 1].end) {
acc.push(curr);
} else {
acc[acc.length - 1].end = dayjs.max(acc[acc.length - 1].end, curr.end);
@@ -26,81 +25,45 @@ const findAvailableSlots = async (cal1, cal2, constraints) => {
}, []);
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')
];
let current = dayjs(searchRange.start);
const rangeEnd = dayjs(searchRange.end);
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;
while (current < rangeEnd) {
const dayStart = current.hour(+whStart[0]).minute(+whStart[1]).second(0);
const dayEnd = current.hour(+whEnd[0]).minute(+whEnd[1]).second(0);
let slotStart = dayStart < current ? current : dayStart;
for (const b of busy) {
if (b.end <= slotStart || b.start >= dayEnd) continue;
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 (b.start > slotStart) {
const slotEnd = dayjs.min(b.start, dayEnd);
let probe = slotStart;
if (dayStart < dayEnd && dayStart >= searchStart && dayEnd <= searchEnd) {
addSlot(dayStart, dayEnd);
while (probe.add(durationMinutes, 'minute') <= slotEnd) {
slots.push({
start: probe.toISOString(),
end: probe.add(durationMinutes, 'minute').toISOString()
});
probe = probe.add(durationMinutes, 'minute');
}
day = day.add(1, 'day');
}
slotStart = dayjs.max(slotStart, b.end);
}
if (slotStart < dayEnd) {
let probe = slotStart;
while (probe.add(durationMinutes, 'minute') <= dayEnd) {
slots.push({
start: probe.toISOString(),
end: probe.add(durationMinutes, 'minute').toISOString()
});
probe = probe.add(durationMinutes, 'minute');
}
}
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');
}
current = current.add(1, 'day').startOf('day');
}
return slots;

View File

@@ -1,69 +1,2 @@
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;

View File

@@ -0,0 +1,50 @@
async function findAvailableSlots(a,b,c){
const d=await import('https://esm.sh/date-fns@3.6.0')
const {parseISO,formatISO,max,min,addMinutes,isBefore}=d
const p=t=>parseISO(t)
const wh=(dt,s)=>{
const [H,M]=s.split(':')
const x=new Date(dt)
x.setHours(H,M,0,0)
return x
}
const rng=[p(c.searchRange.start),p(c.searchRange.end)]
const dur=c.durationMinutes
const norm=x=>x.map(v=>[p(v.start),p(v.end)])
const A=norm(a),B=norm(b)
const merge=x=>{
let r=x.sort((x,y)=>x[0]-y[0]),o=[r[0]]
for(let i=1;i<r.length;i++){
let [s,e]=r[i],l=o[o.length-1]
if(s<=l[1]) l[1]=new Date(Math.max(e,l[1]))
else o.push([s,e])
}
return o
}
const busy=merge(A.concat(B))
const free=[]
let cur=rng[0]
for(let i=0;i<=busy.length;i++){
let s=i<busy.length?max(cur,busy[i][1]):cur
let e=i<busy.length?max(cur,busy[i][0]):rng[1]
if(i<busy.length){
if(busy[i][0]>cur) free.push([cur,busy[i][0]])
cur=max(cur,busy[i][1])
}else free.push([s,e])
}
const out=[]
for(let [s,e]of free){
s=max(s,rng[0])
e=min(e,rng[1])
let ws=wh(s,c.workHours.start)
let we=wh(s,c.workHours.end)
if(ws>we){let t=ws;ws=we;we=t}
let ss=max(s,ws),ee=min(e,we)
while(isBefore(addMinutes(ss,dur),ee)){
out.push({start:formatISO(ss),end:formatISO(addMinutes(ss,dur))})
ss=addMinutes(ss,dur)
}
}
return out
}
export default findAvailableSlots;

View File

@@ -1,59 +1,55 @@
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;
const luxon=import('https://cdn.skypack.dev/luxon');
const findAvailableSlots=async(c1,c2,k)=>{
const {DateTime}=await luxon;
const {durationMinutes:d,searchRange:r,workHours:w}=k;
const zone=DateTime.fromISO(r.start).zoneName;
const iso=v=>DateTime.fromISO(v,{zone});
const rangeStart=iso(r.start);
const rangeEnd=iso(r.end);
if(rangeEnd<=rangeStart)return[];
const [hs,ms]=w.start.split(':').map(Number);
const [he,me]=w.end.split(':').map(Number);
const daysEnd=rangeEnd.startOf('day');
const windows=[];
for(let day=rangeStart.startOf('day');day<=daysEnd;day=day.plus({days:1})){
let s=day.set({hour:hs,minute:ms,second:0,millisecond:0});
let e=day.set({hour:he,minute:me,second:0,millisecond:0});
if(e<=s||e<=rangeStart||s>=rangeEnd)continue;
if(s<rangeStart)s=rangeStart;
if(e>rangeEnd)e=rangeEnd;
windows.push({start:s,end:e});
}
return slots;
if(!windows.length)return[];
const busy=[...c1,...c2].map(v=>{
let s=iso(v.start),e=iso(v.end);
if(e<=s||e<=rangeStart||s>=rangeEnd)return null;
if(s<rangeStart)s=rangeStart;
if(e>rangeEnd)e=rangeEnd;
return{start:s,end:e};
}).filter(Boolean).sort((a,b)=>a.start.valueOf()-b.start.valueOf());
const merged=[];
for(const slot of busy){
const last=merged[merged.length-1];
if(last&&slot.start<=last.end){
if(slot.end>last.end)last.end=slot.end;
}else merged.push({start:slot.start,end:slot.end});
}
const out=[];
const push=(s,e)=>e.diff(s,'minutes').minutes>=d&&out.push({start:s.toISO(),end:e.toISO()});
for(const wSlot of windows){
let cur=wSlot.start;
for(const b of merged){
if(b.start>=wSlot.end)break;
if(b.end<=wSlot.start)continue;
const bs=b.start>wSlot.start?b.start:wSlot.start;
const be=b.end<wSlot.end?b.end:wSlot.end;
if(bs>cur)push(cur,bs);
if(be>cur)cur=be;
if(cur>=wSlot.end)break;
}
if(cur<wSlot.end)push(cur,wSlot.end);
}
return out;
};
export default findAvailableSlots;