mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-03-17 11:21:00 +00:00
Refactor: Remove stale benchmark outputs
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
async function findAvailableSlots(calendar1, calendar2, constraints) {
|
||||
const { DateTime, Interval } = await import("https://cdn.jsdelivr.net/npm/luxon@3/+esm");
|
||||
|
||||
const { durationMinutes, searchRange, workHours } = constraints;
|
||||
const dur = durationMinutes;
|
||||
|
||||
const rangeStart = DateTime.fromISO(searchRange.start, { zone: "utc" });
|
||||
const rangeEnd = DateTime.fromISO(searchRange.end, { zone: "utc" });
|
||||
|
||||
const [whStartH, whStartM] = workHours.start.split(":").map(Number);
|
||||
const [whEndH, whEndM] = workHours.end.split(":").map(Number);
|
||||
|
||||
const allBusy = [...calendar1, ...calendar2]
|
||||
.map(b => ({
|
||||
s: DateTime.fromISO(b.start, { zone: "utc" }),
|
||||
e: DateTime.fromISO(b.end, { zone: "utc" })
|
||||
}))
|
||||
.sort((a, b) => a.s - b.s);
|
||||
|
||||
const merged = [];
|
||||
for (const slot of allBusy) {
|
||||
if (merged.length && slot.s <= merged[merged.length - 1].e) {
|
||||
merged[merged.length - 1].e = DateTime.max(merged[merged.length - 1].e, slot.e);
|
||||
} else {
|
||||
merged.push({ s: slot.s, e: slot.e });
|
||||
}
|
||||
}
|
||||
|
||||
const freePeriods = [];
|
||||
let cursor = rangeStart;
|
||||
|
||||
for (const busy of merged) {
|
||||
if (busy.s > cursor) {
|
||||
freePeriods.push({ s: cursor, e: DateTime.min(busy.s, rangeEnd) });
|
||||
}
|
||||
cursor = DateTime.max(cursor, busy.e);
|
||||
if (cursor >= rangeEnd) break;
|
||||
}
|
||||
|
||||
if (cursor < rangeEnd) {
|
||||
freePeriods.push({ s: cursor, e: rangeEnd });
|
||||
}
|
||||
|
||||
const workConstrained = [];
|
||||
|
||||
for (const free of freePeriods) {
|
||||
let day = free.s.startOf("day");
|
||||
const lastDay = free.e.startOf("day");
|
||||
|
||||
while (day <= lastDay) {
|
||||
const whS = day.set({ hour: whStartH, minute: whStartM, second: 0, millisecond: 0 });
|
||||
const whE = day.set({ hour: whEndH, minute: whEndM, second: 0, millisecond: 0 });
|
||||
|
||||
const overlapStart = DateTime.max(free.s, whS, rangeStart);
|
||||
const overlapEnd = DateTime.min(free.e, whE, rangeEnd);
|
||||
|
||||
if (overlapStart < overlapEnd) {
|
||||
workConstrained.push({ s: overlapStart, e: overlapEnd });
|
||||
}
|
||||
|
||||
day = day.plus({ days: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const period of workConstrained) {
|
||||
let slotStart = period.s;
|
||||
|
||||
while (true) {
|
||||
const slotEnd = slotStart.plus({ minutes: dur });
|
||||
if (slotEnd > period.e) break;
|
||||
results.push({
|
||||
start: slotStart.toISO(),
|
||||
end: slotEnd.toISO()
|
||||
});
|
||||
slotStart = slotEnd;
|
||||
}
|
||||
}
|
||||
|
||||
results.sort((a, b) => a.start.localeCompare(b.start));
|
||||
|
||||
return results;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 11.229s
|
||||
// Result: PASS
|
||||
@@ -1,88 +0,0 @@
|
||||
async function findAvailableSlots(calendar1, calendar2, constraints) {
|
||||
const { DateTime, Interval } = await import("https://cdn.jsdelivr.net/npm/luxon@3/+esm");
|
||||
|
||||
const { durationMinutes, searchRange, workHours } = constraints;
|
||||
const rangeStart = DateTime.fromISO(searchRange.start, { zone: "utc" });
|
||||
const rangeEnd = DateTime.fromISO(searchRange.end, { zone: "utc" });
|
||||
const [whStartH, whStartM] = workHours.start.split(":").map(Number);
|
||||
const [whEndH, whEndM] = workHours.end.split(":").map(Number);
|
||||
|
||||
const allBusy = [...calendar1, ...calendar2]
|
||||
.map(b => ({
|
||||
s: DateTime.fromISO(b.start, { zone: "utc" }),
|
||||
e: DateTime.fromISO(b.end, { zone: "utc" })
|
||||
}))
|
||||
.sort((a, b) => a.s - b.s);
|
||||
|
||||
const merged = [];
|
||||
for (const slot of allBusy) {
|
||||
if (merged.length && slot.s <= merged[merged.length - 1].e) {
|
||||
merged[merged.length - 1].e = DateTime.max(merged[merged.length - 1].e, slot.e);
|
||||
} else {
|
||||
merged.push({ s: slot.s, e: slot.e });
|
||||
}
|
||||
}
|
||||
|
||||
const freePeriods = [];
|
||||
let cursor = rangeStart;
|
||||
for (const busy of merged) {
|
||||
if (busy.s > cursor) {
|
||||
freePeriods.push({ s: cursor, e: DateTime.min(busy.s, rangeEnd) });
|
||||
}
|
||||
cursor = DateTime.max(cursor, busy.e);
|
||||
}
|
||||
if (cursor < rangeEnd) {
|
||||
freePeriods.push({ s: cursor, e: rangeEnd });
|
||||
}
|
||||
|
||||
const workWindows = [];
|
||||
let day = rangeStart.startOf("day");
|
||||
const lastDay = rangeEnd.startOf("day").plus({ days: 1 });
|
||||
|
||||
while (day < lastDay) {
|
||||
const ws = day.set({ hour: whStartH, minute: whStartM, second: 0, millisecond: 0 });
|
||||
const we = day.set({ hour: whEndH, minute: whEndM, second: 0, millisecond: 0 });
|
||||
if (we > ws) {
|
||||
const clampedStart = DateTime.max(ws, rangeStart);
|
||||
const clampedEnd = DateTime.min(we, rangeEnd);
|
||||
if (clampedStart < clampedEnd) {
|
||||
workWindows.push({ s: clampedStart, e: clampedEnd });
|
||||
}
|
||||
}
|
||||
day = day.plus({ days: 1 });
|
||||
}
|
||||
|
||||
const effectiveFree = [];
|
||||
for (const free of freePeriods) {
|
||||
for (const ww of workWindows) {
|
||||
const s = DateTime.max(free.s, ww.s);
|
||||
const e = DateTime.min(free.e, ww.e);
|
||||
if (s < e) {
|
||||
effectiveFree.push({ s, e });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
effectiveFree.sort((a, b) => a.s - b.s);
|
||||
|
||||
const results = [];
|
||||
const dur = { minutes: durationMinutes };
|
||||
|
||||
for (const period of effectiveFree) {
|
||||
let slotStart = period.s;
|
||||
while (true) {
|
||||
const slotEnd = slotStart.plus(dur);
|
||||
if (slotEnd > period.e) break;
|
||||
results.push({
|
||||
start: slotStart.toISO(),
|
||||
end: slotEnd.toISO()
|
||||
});
|
||||
slotStart = slotEnd;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 12.695s
|
||||
// Result: PASS
|
||||
@@ -1,56 +0,0 @@
|
||||
export async function findAvailableSlots(cal1, cal2, { durationMinutes, searchRange, workHours }) {
|
||||
const dayjs = (await import('https://cdn.skypack.dev/dayjs')).default;
|
||||
const utc = (await import('https://cdn.skypack.dev/dayjs/plugin/utc')).default;
|
||||
dayjs.extend(utc);
|
||||
|
||||
const u = (v) => dayjs.utc(v);
|
||||
const sR = u(searchRange.start);
|
||||
const eR = u(searchRange.end);
|
||||
const [sh, sm] = workHours.start.split(':');
|
||||
const [eh, em] = workHours.end.split(':');
|
||||
|
||||
let blocked = [...cal1, ...cal2].map((b) => ({
|
||||
s: u(b.start),
|
||||
e: u(b.end)
|
||||
}));
|
||||
|
||||
for (let c = sR.startOf('d'); c <= eR; c = c.add(1, 'd')) {
|
||||
blocked.push(
|
||||
{ s: c, e: c.set('h', sh).set('m', sm) },
|
||||
{ s: c.set('h', eh).set('m', em), e: c.add(1, 'd') }
|
||||
);
|
||||
}
|
||||
|
||||
const merged = blocked
|
||||
.map((b) => ({
|
||||
s: b.s < sR ? sR : b.s,
|
||||
e: b.e > eR ? eR : b.e
|
||||
}))
|
||||
.filter((b) => b.s < b.e)
|
||||
.sort((a, b) => a.s - b.s)
|
||||
.reduce((acc, b) => {
|
||||
const p = acc[acc.length - 1];
|
||||
p && b.s <= p.e ? (p.e = b.e > p.e ? b.e : p.e) : acc.push(b);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const avail = [];
|
||||
let cursor = sR;
|
||||
|
||||
[...merged, { s: eR, e: eR }].forEach((b) => {
|
||||
while (cursor.add(durationMinutes, 'm') <= b.s) {
|
||||
const next = cursor.add(durationMinutes, 'm');
|
||||
avail.push({
|
||||
start: cursor.toISOString(),
|
||||
end: next.toISOString()
|
||||
});
|
||||
cursor = next;
|
||||
}
|
||||
if (b.e > cursor) cursor = b.e;
|
||||
});
|
||||
|
||||
return avail;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 35.904s
|
||||
// Result: PASS
|
||||
@@ -1,94 +0,0 @@
|
||||
export async function findAvailableSlots(calendarA, calendarB, constraints) {
|
||||
const { durationMinutes, searchRange, workHours } = constraints;
|
||||
const { parseISO, addMinutes, differenceInMinutes, min, max, formatISO, isAfter, isBefore, isEqual, parse } = await import('https://cdn.jsdelivr.net/npm/date-fns@3.6.0/+esm');
|
||||
|
||||
const searchStart = parseISO(searchRange.start);
|
||||
const searchEnd = parseISO(searchRange.end);
|
||||
const workStart = parse(workHours.start, 'HH:mm', searchStart);
|
||||
const workEnd = parse(workHours.end, 'HH:mm', searchStart);
|
||||
|
||||
const allBusy = [...calendarA, ...calendarB].map(slot => ({
|
||||
start: parseISO(slot.start),
|
||||
end: parseISO(slot.end)
|
||||
})).sort((a, b) => a.start - b.start);
|
||||
|
||||
const mergedBusy = [];
|
||||
for (const slot of allBusy) {
|
||||
if (!mergedBusy.length || isAfter(slot.start, mergedBusy[mergedBusy.length - 1].end)) {
|
||||
mergedBusy.push({ start: slot.start, end: slot.end });
|
||||
} else {
|
||||
mergedBusy[mergedBusy.length - 1].end = max([mergedBusy[mergedBusy.length - 1].end, slot.end]);
|
||||
}
|
||||
}
|
||||
|
||||
const freePeriods = [];
|
||||
let currentStart = searchStart;
|
||||
|
||||
for (const busy of mergedBusy) {
|
||||
if (isBefore(currentStart, busy.start)) {
|
||||
freePeriods.push({ start: currentStart, end: busy.start });
|
||||
}
|
||||
currentStart = max([currentStart, busy.end]);
|
||||
}
|
||||
|
||||
if (isBefore(currentStart, searchEnd)) {
|
||||
freePeriods.push({ start: currentStart, end: searchEnd });
|
||||
}
|
||||
|
||||
const slots = [];
|
||||
|
||||
for (const period of freePeriods) {
|
||||
const periodStart = max([period.start, searchStart]);
|
||||
const periodEnd = min([period.end, searchEnd]);
|
||||
|
||||
let slotStart = periodStart;
|
||||
|
||||
while (true) {
|
||||
const slotEnd = addMinutes(slotStart, durationMinutes);
|
||||
|
||||
if (isAfter(slotEnd, periodEnd) || isAfter(slotEnd, searchEnd)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const slotWorkStart = new Date(slotStart);
|
||||
const slotWorkEnd = new Date(slotStart);
|
||||
slotWorkStart.setUTCHours(workStart.getUTCHours(), workStart.getUTCMinutes(), 0, 0);
|
||||
slotWorkEnd.setUTCHours(workEnd.getUTCHours(), workEnd.getUTCMinutes(), 0, 0);
|
||||
|
||||
const adjustedSlotStart = max([slotStart, slotWorkStart]);
|
||||
|
||||
if (differenceInMinutes(slotEnd, adjustedSlotStart) >= durationMinutes) {
|
||||
const finalSlotStart = adjustedSlotStart;
|
||||
const finalSlotEnd = addMinutes(finalSlotStart, durationMinutes);
|
||||
|
||||
if (!isAfter(finalSlotEnd, periodEnd) && !isAfter(finalSlotEnd, searchEnd) && !isAfter(finalSlotEnd, slotWorkEnd)) {
|
||||
slots.push({ start: formatISO(finalSlotStart), end: formatISO(finalSlotEnd) });
|
||||
}
|
||||
}
|
||||
|
||||
slotStart = addMinutes(slotStart, durationMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
const sortedSlots = slots.sort((a, b) => parseISO(a.start) - parseISO(b.start));
|
||||
|
||||
const nonOverlappingSlots = [];
|
||||
let lastEnd = null;
|
||||
|
||||
for (const slot of sortedSlots) {
|
||||
const slotStart = parseISO(slot.start);
|
||||
const slotEnd = parseISO(slot.end);
|
||||
|
||||
if (!lastEnd || !isBefore(slotStart, lastEnd)) {
|
||||
if (!lastEnd || isAfter(slotStart, lastEnd)) {
|
||||
nonOverlappingSlots.push(slot);
|
||||
lastEnd = slotEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nonOverlappingSlots;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 14.885s
|
||||
// Result: FAIL
|
||||
@@ -1,142 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
export async function findAvailableSlots(busyA, busyB, constraints) {
|
||||
const {
|
||||
addMinutes,
|
||||
compareAsc,
|
||||
eachMinuteOfInterval,
|
||||
isBefore,
|
||||
isEqual,
|
||||
isValid,
|
||||
parseISO,
|
||||
startOfDay,
|
||||
} = await import('https://cdn.skypack.dev/date-fns@2.30.0');
|
||||
|
||||
const c = constraints || {};
|
||||
const durationMinutes = (c && c.durationMinutes) || 30;
|
||||
if (!Number.isInteger(durationMinutes) || durationMinutes <= 0) throw new Error('durationMinutes must be a positive integer');
|
||||
const range = c && c.searchRange || {};
|
||||
const rangeStart = parseISO(range.start);
|
||||
const rangeEnd = parseISO(range.end);
|
||||
if (!isValid(rangeStart) || !isValid(rangeEnd) || !isBefore(rangeStart, rangeEnd)) throw new Error('Invalid searchRange');
|
||||
const wh = c && c.workHours || {};
|
||||
const [whStartH, whStartM] = String(wh.start || '09:00').split(':').map(n => parseInt(n, 10));
|
||||
const [whEndH, whEndM] = String(wh.end || '17:00').split(':').split(':').map(n => parseInt(n, 10));
|
||||
const WH_ST = whStartH * 60 + (isNaN(whStartM) ? 0 : whStartM);
|
||||
const WH_ET = whEndH * 60 + (isNaN(whEndM) ? 0 : whEndM);
|
||||
if (!(0 <= WH_ST && WH_ST < WH_ET && WH_ET <= 1440)) throw new Error('Invalid workHours');
|
||||
|
||||
const toEpoch = d => d.getTime();
|
||||
const fmtTime = d => {
|
||||
const t = d.getUTCHours().toString().padStart(2, '0') + ':' + d.getUTCMinutes().toString().padStart(2, '0');
|
||||
return t;
|
||||
};
|
||||
|
||||
const parseBusy = slot => {
|
||||
const s = parseISO(slot.start);
|
||||
const e = parseISO(slot.end);
|
||||
if (!isValid(s) || !isValid(e) || !isBefore(s, e) && !isEqual(s, e)) throw new Error('Invalid busy slot');
|
||||
return { s, e };
|
||||
};
|
||||
|
||||
const busy = [...busyA, ...busyB].map(parseBusy)
|
||||
.filter(({ s, e }) => isBefore(rangeStart, e) && !isBefore(s, rangeEnd))
|
||||
.map(({ s, e }) => ({ s: isBefore(s, rangeStart) ? rangeStart : s, e: isBefore(rangeEnd, e) ? rangeEnd : e }))
|
||||
.sort((a, b) => compareAsc(a.s, b.s));
|
||||
|
||||
const merged = [];
|
||||
for (const cur of busy) {
|
||||
if (merged.length === 0) {
|
||||
merged.push({ ...cur });
|
||||
continue;
|
||||
}
|
||||
const last = merged[merged.length - 1];
|
||||
if (isBefore(last.e, cur.s) || isEqual(last.e, cur.s)) {
|
||||
merged.push({ ...cur });
|
||||
} else {
|
||||
if (isBefore(cur.e, last.e)) continue;
|
||||
last.e = cur.e;
|
||||
}
|
||||
}
|
||||
|
||||
const findNext = (t, arr, lo, hi) => {
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (isBefore(arr[mid].e, t) || isEqual(arr[mid].e, t)) lo = mid + 1;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
return lo < arr.length ? lo : -1;
|
||||
};
|
||||
|
||||
const available = [];
|
||||
let cursor = rangeStart;
|
||||
let idx = 0;
|
||||
while (idx < merged.length && !isBefore(rangeEnd, merged[idx].s)) {
|
||||
const blockStart = merged[idx].s;
|
||||
const blockEnd = merged[idx].e;
|
||||
if (isBefore(cursor, blockStart)) {
|
||||
available.push({ start: cursor, end: blockStart });
|
||||
}
|
||||
if (isBefore(blockEnd, cursor)) {
|
||||
idx = findNext(cursor, merged, idx + 1, merged.length - 1);
|
||||
if (idx === -1) break;
|
||||
continue;
|
||||
}
|
||||
cursor = blockEnd;
|
||||
idx = idx + 1;
|
||||
}
|
||||
if (isBefore(cursor, rangeEnd)) available.push({ start: cursor, end: rangeEnd });
|
||||
|
||||
const isSameUTCDay = (a, b) =>
|
||||
a.getUTCFullYear() === b.getUTCFullYear() &&
|
||||
a.getUTCMonth() === b.getUTCMonth() &&
|
||||
a.getUTCDate() === b.getUTCDate();
|
||||
|
||||
const startOfNextDayUTC = d => addMinutes(startOfDay(d), 24 * 60);
|
||||
|
||||
const clipSegmentToWorkHours = (segStart, segEnd) => {
|
||||
const out = [];
|
||||
let day = startOfDay(segStart);
|
||||
while (isBefore(day, segEnd)) {
|
||||
const dayStart = isBefore(day, segStart) ? segStart : day;
|
||||
const dayNext = startOfNextDayUTC(day);
|
||||
const dayEnd = isBefore(segEnd, dayNext) ? segEnd : dayNext;
|
||||
const segST = (dayStart.getUTCHours() * 60 + dayStart.getUTCMinutes()) | 0;
|
||||
const segET = (dayEnd.getUTCHours() * 60 + dayEnd.getUTCMinutes()) | 0;
|
||||
const st = Math.max(segST, WH_ST);
|
||||
const et = Math.min(segET, WH_ET);
|
||||
if (st < et) {
|
||||
const clipStart = addMinutes(day, st);
|
||||
const clipEnd = addMinutes(day, et);
|
||||
out.push({ start: clipStart, end: clipEnd });
|
||||
}
|
||||
day = dayNext;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const slots = [];
|
||||
for (const seg of available) {
|
||||
const clips = clipSegmentToWorkHours(seg.start, seg.end);
|
||||
for (const clip of clips) {
|
||||
const clipLen = Math.floor((toEpoch(clip.end) - toEpoch(clip.start)) / 60000);
|
||||
const count = Math.floor(clipLen / durationMinutes);
|
||||
if (count <= 0) continue;
|
||||
let cur = clip.start;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const s = cur;
|
||||
const e = addMinutes(s, durationMinutes);
|
||||
if (isBefore(e, clip.end) || isEqual(e, clip.end)) {
|
||||
if (isBefore(rangeStart, s) && !isBefore(rangeEnd, e)) {
|
||||
slots.push({ start: s.toISOString(), end: e.toISOString() });
|
||||
}
|
||||
}
|
||||
cur = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 36.069s
|
||||
// Result: FAIL
|
||||
@@ -1,56 +0,0 @@
|
||||
export async function findAvailableSlots(cal1, cal2, { durationMinutes, searchRange, workHours }) {
|
||||
const { parseISO, compareAsc, addMinutes } = await import('https://cdn.skypack.dev/date-fns');
|
||||
|
||||
const s = parseISO(searchRange.start);
|
||||
const e = parseISO(searchRange.end);
|
||||
|
||||
const [whS, whE] = [workHours.start, workHours.end].map(t => {
|
||||
const [h, m] = t.split(':').map(Number);
|
||||
return h * 60 + m;
|
||||
});
|
||||
|
||||
const inWH = (d, de) => {
|
||||
const g = t => t.getUTCHours() * 60 + t.getUTCMinutes();
|
||||
return g(d) >= whS && g(de) <= whE;
|
||||
};
|
||||
|
||||
const busy = [...cal1, ...cal2]
|
||||
.map(({ start, end }) => ({ s: parseISO(start), e: parseISO(end) }))
|
||||
.filter(({ s: bs, e: be }) => compareAsc(be, s) > 0 && compareAsc(bs, e) < 0)
|
||||
.map(({ s: bs, e: be }) => ({
|
||||
s: compareAsc(bs, s) < 0 ? s : bs,
|
||||
e: compareAsc(be, e) > 0 ? e : be
|
||||
}))
|
||||
.sort((a, b) => compareAsc(a.s, b.s));
|
||||
|
||||
const merged = [];
|
||||
for (const { s: bs, e: be } of busy) {
|
||||
const last = merged[merged.length - 1];
|
||||
if (!last || compareAsc(bs, last.e) >= 0) merged.push({ s: bs, e: be });
|
||||
else if (compareAsc(be, last.e) > 0) last.e = be;
|
||||
}
|
||||
|
||||
const free = [];
|
||||
let cur = s;
|
||||
for (const { s: ms, e: me } of merged) {
|
||||
if (compareAsc(cur, ms) < 0) free.push({ s: cur, e: ms });
|
||||
cur = compareAsc(me, cur) > 0 ? me : cur;
|
||||
}
|
||||
if (compareAsc(cur, e) < 0) free.push({ s: cur, e });
|
||||
|
||||
const slots = [];
|
||||
for (const { s: fs, e: fe } of free) {
|
||||
let cs = fs;
|
||||
while (true) {
|
||||
const ce = addMinutes(cs, durationMinutes);
|
||||
if (compareAsc(ce, fe) > 0) break;
|
||||
if (inWH(cs, ce)) slots.push({ start: cs.toISOString(), end: ce.toISOString() });
|
||||
cs = addMinutes(cs, durationMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 289.823s
|
||||
// Result: PASS
|
||||
@@ -1,38 +0,0 @@
|
||||
async function findAvailableSlots(a,b,o){
|
||||
const {default:d}=await import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/esm/index.js')
|
||||
const {default:u}=await import('https://cdn.jsdelivr.net/npm/dayjs@1.11.10/esm/plugin/utc.js')
|
||||
d.extend(u)
|
||||
const s=d.utc(o.searchRange.start),e=d.utc(o.searchRange.end)
|
||||
const S=s.valueOf(),E=e.valueOf(),t=o.durationMinutes*6e4
|
||||
const busy=[...a,...b].map(v=>[d.utc(v.start).valueOf(),d.utc(v.end).valueOf()]).filter(v=>v[1]>S&&v[0]<E).sort((i,j)=>i[0]-j[0])
|
||||
const m=[]
|
||||
for(const [b0,b1]of busy){
|
||||
const x=Math.max(b0,S),y=Math.min(b1,E)
|
||||
if(!m.length||x>m[m.length-1][1])m.push([x,y])
|
||||
else m[m.length-1][1]=Math.max(m[m.length-1][1],y)
|
||||
}
|
||||
const r=[],ws=o.workHours.start,we=o.workHours.end
|
||||
let j=0
|
||||
for(let day=s.startOf('day');day.isBefore(e);day=day.add(1,'day')){
|
||||
let w0=d.utc(`${day.format('YYYY-MM-DD')}T${ws}Z`).valueOf(),w1=d.utc(`${day.format('YYYY-MM-DD')}T${we}Z`).valueOf()
|
||||
if(w1<=S||w0>=E)continue
|
||||
if(w0<S)w0=S
|
||||
if(w1>E)w1=E
|
||||
if(w0>=w1)continue
|
||||
while(j<m.length&&m[j][1]<=w0)j++
|
||||
let p=w0
|
||||
for(let k=j;k<m.length&&m[k][0]<w1;k++){
|
||||
if(m[k][0]>p){
|
||||
for(let a=p;a+t<=Math.min(m[k][0],w1);a+=t)r.push({start:new Date(a).toISOString(),end:new Date(a+t).toISOString()})
|
||||
}
|
||||
p=Math.max(p,m[k][1])
|
||||
}
|
||||
if(p<w1){
|
||||
for(let a=p;a+t<=w1;a+=t)r.push({start:new Date(a).toISOString(),end:new Date(a+t).toISOString()})
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 62.189s
|
||||
// Result: FAIL
|
||||
@@ -1,93 +0,0 @@
|
||||
const loadDay = (() => {
|
||||
let memo
|
||||
return () => memo ?? (memo = (async () => {
|
||||
const [core, utc] = await Promise.all([
|
||||
import('https://esm.sh/dayjs@1.11.13?target=esnext'),
|
||||
import('https://esm.sh/dayjs@1.11.13/plugin/utc?target=esnext')
|
||||
])
|
||||
const d = core.default
|
||||
d.extend(utc.default)
|
||||
return d
|
||||
})())
|
||||
})()
|
||||
|
||||
const findAvailableSlots = async (c1 = [], c2 = [], cfg = {}) => {
|
||||
const d = await loadDay()
|
||||
const u = v => d.utc(v)
|
||||
const {
|
||||
durationMinutes: dm,
|
||||
searchRange: sr = {},
|
||||
workHours: wh = {}
|
||||
} = cfg
|
||||
const dur = Number(dm)
|
||||
if(!Number.isFinite(dur) || dur <= 0 || !sr.start || !sr.end || !wh.start || !wh.end) return []
|
||||
const s = u(sr.start)
|
||||
const e = u(sr.end)
|
||||
if(!s.isValid() || !e.isValid() || !s.isBefore(e)) return []
|
||||
const toMin = v => {
|
||||
const [h, m = 0] = String(v).split(':').map(Number)
|
||||
if(!Number.isFinite(h) || !Number.isFinite(m)) return null
|
||||
if(h < 0 || h > 24 || m < 0 || m > 59) return null
|
||||
if(h === 24 && m > 0) return null
|
||||
return h * 60 + m
|
||||
}
|
||||
const ws = toMin(wh.start)
|
||||
const we = toMin(wh.end)
|
||||
if(ws == null || we == null || ws >= we) return []
|
||||
const list = [
|
||||
...(Array.isArray(c1) ? c1 : []),
|
||||
...(Array.isArray(c2) ? c2 : [])
|
||||
]
|
||||
const busy = list.map(v => {
|
||||
if(!v?.start || !v?.end) return null
|
||||
const st = u(v.start)
|
||||
const en = u(v.end)
|
||||
if(!st.isValid() || !en.isValid() || !st.isBefore(en)) return null
|
||||
if(!en.isAfter(s) || !st.isBefore(e)) return null
|
||||
return {
|
||||
start: st.isBefore(s) ? s : st,
|
||||
end: en.isAfter(e) ? e : en
|
||||
}
|
||||
}).filter(Boolean).sort((a, b) => a.start.valueOf() - b.start.valueOf())
|
||||
const merged = []
|
||||
for(const slot of busy){
|
||||
if(!merged.length){
|
||||
merged.push({...slot})
|
||||
continue
|
||||
}
|
||||
const last = merged[merged.length - 1]
|
||||
if(slot.start.isAfter(last.end)) merged.push({...slot})
|
||||
else if(slot.end.isAfter(last.end)) last.end = slot.end
|
||||
}
|
||||
const free = []
|
||||
let cur = s
|
||||
for(const slot of merged){
|
||||
if(slot.start.isAfter(cur)) free.push({start: cur, end: slot.start})
|
||||
if(slot.end.isAfter(cur)) cur = slot.end
|
||||
}
|
||||
if(cur.isBefore(e)) free.push({start: cur, end: e})
|
||||
if(!free.length) return []
|
||||
const step = dur * 60000
|
||||
if(!Number.isFinite(step) || step <= 0) return []
|
||||
const out = []
|
||||
for(const seg of free){
|
||||
for(let day = seg.start.startOf('day'); day.isBefore(seg.end); day = day.add(1, 'day')){
|
||||
const wsStart = day.add(ws, 'minute')
|
||||
const wsEnd = day.add(we, 'minute')
|
||||
const start = wsStart.isAfter(seg.start) ? wsStart : seg.start
|
||||
const end = wsEnd.isBefore(seg.end) ? wsEnd : seg.end
|
||||
if(!start.isBefore(end)) continue
|
||||
const limit = end.valueOf()
|
||||
for(let t = start.valueOf(); t + step <= limit; t += step){
|
||||
out.push({
|
||||
start: d.utc(t).toISOString(),
|
||||
end: d.utc(t + step).toISOString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 211.798s
|
||||
// Result: PASS
|
||||
@@ -1,96 +0,0 @@
|
||||
let _lx;
|
||||
const _loadLuxon = () =>
|
||||
_lx || (_lx = import("https://cdn.jsdelivr.net/npm/luxon@3.4.4/build/es6/luxon.js"));
|
||||
|
||||
async function findAvailableSlots(calendarA, calendarB, constraints) {
|
||||
const { DateTime } = await _loadLuxon();
|
||||
const M = 6e4, DAY = 1440, Z = { zone: "utc" };
|
||||
|
||||
const a = Array.isArray(calendarA) ? calendarA : [];
|
||||
const b = Array.isArray(calendarB) ? calendarB : [];
|
||||
const o = constraints && typeof constraints === "object" ? constraints : {};
|
||||
|
||||
const durMin = Number(o.durationMinutes);
|
||||
if (!(durMin > 0)) return [];
|
||||
const dur = durMin * M;
|
||||
|
||||
const sr = o.searchRange || {};
|
||||
const rs = DateTime.fromISO(sr.start || "", Z);
|
||||
const re = DateTime.fromISO(sr.end || "", Z);
|
||||
if (!rs.isValid || !re.isValid) return [];
|
||||
const r0 = rs.toMillis(), r1 = re.toMillis();
|
||||
if (r0 >= r1) return [];
|
||||
|
||||
const wh = o.workHours || {};
|
||||
const toMin = t => {
|
||||
const [h, m] = String(t).split(":").map(Number);
|
||||
if (!Number.isFinite(h) || !Number.isFinite(m)) return NaN;
|
||||
if (h === 24 && m === 0) return DAY;
|
||||
if (h < 0 || h > 23 || m < 0 || m > 59) return NaN;
|
||||
return h * 60 + m;
|
||||
};
|
||||
const ws = toMin(wh.start), we = toMin(wh.end);
|
||||
if (!Number.isFinite(ws) || !Number.isFinite(we)) return [];
|
||||
|
||||
const clamp = (s, e) => {
|
||||
s = Math.max(s, r0);
|
||||
e = Math.min(e, r1);
|
||||
return s < e ? [s, e] : null;
|
||||
};
|
||||
|
||||
const busy = [];
|
||||
const add = (s, e) => {
|
||||
const c = clamp(s, e);
|
||||
if (c) busy.push(c);
|
||||
};
|
||||
|
||||
const addEvent = ev => {
|
||||
const s = DateTime.fromISO(ev?.start || "", Z);
|
||||
const e = DateTime.fromISO(ev?.end || "", Z);
|
||||
if (!s.isValid || !e.isValid) return;
|
||||
const x = s.toMillis(), y = e.toMillis();
|
||||
if (x < y) add(x, y);
|
||||
};
|
||||
|
||||
a.forEach(addEvent);
|
||||
b.forEach(addEvent);
|
||||
|
||||
for (let d = rs.startOf("day"); d.toMillis() < r1; d = d.plus({ days: 1 })) {
|
||||
const base = d.toMillis();
|
||||
const off = (x, y) => add(base + x * M, base + y * M);
|
||||
|
||||
if (ws === we) off(0, DAY);
|
||||
else if (ws < we) {
|
||||
if (ws) off(0, ws);
|
||||
if (we < DAY) off(we, DAY);
|
||||
} else off(we, ws);
|
||||
}
|
||||
|
||||
busy.sort((x, y) => x[0] - y[0] || x[1] - y[1]);
|
||||
|
||||
const merged = [];
|
||||
for (const [s, e] of busy) {
|
||||
const last = merged[merged.length - 1];
|
||||
if (!last || s > last[1]) merged.push([s, e]);
|
||||
else last[1] = Math.max(last[1], e);
|
||||
}
|
||||
|
||||
const out = [];
|
||||
const iso = ms => new Date(ms).toISOString();
|
||||
const pushSlot = (s, e) => out.push({ start: iso(s), end: iso(e) });
|
||||
|
||||
let cur = r0;
|
||||
for (const [s, e] of merged) {
|
||||
if (cur < s) {
|
||||
for (let t = cur; t + dur <= s; t += dur) pushSlot(t, t + dur);
|
||||
}
|
||||
if (e > cur) cur = e;
|
||||
if (cur >= r1) return out;
|
||||
}
|
||||
|
||||
for (let t = cur; t + dur <= r1; t += dur) pushSlot(t, t + dur);
|
||||
return out;
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
// Generation time: 822.838s
|
||||
// Result: PASS
|
||||
Reference in New Issue
Block a user