mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-01-14 00:27:55 +00:00
77 lines
3.0 KiB
JavaScript
77 lines
3.0 KiB
JavaScript
async function findAvailableSlots(cal1, cal2, constraints) {
|
|
const { DateTime, Interval } = await import('https://cdn.skypack.dev/luxon');
|
|
const { durationMinutes, searchRange, workHours } = constraints;
|
|
const searchStart = DateTime.fromISO(searchRange.start);
|
|
const searchEnd = DateTime.fromISO(searchRange.end);
|
|
const searchInterval = Interval.fromDateTimes(searchStart, searchEnd);
|
|
const zone = searchStart.zone;
|
|
const workStart = DateTime.fromFormat(workHours.start, 'HH:mm', { zone });
|
|
const workEnd = DateTime.fromFormat(workHours.end, 'HH:mm', { zone });
|
|
let busies = [];
|
|
for (const slot of [...cal1, ...cal2]) {
|
|
const iv = Interval.fromISO(`${slot.start}/${slot.end}`);
|
|
const i = iv.intersection(searchInterval);
|
|
if (i?.isValid) busies.push(i);
|
|
}
|
|
let nonWork = [];
|
|
let currentDay = searchStart.startOf('day');
|
|
const endDay = searchEnd.endOf('day');
|
|
while (currentDay <= endDay) {
|
|
const dayStart = currentDay;
|
|
const dayEnd = currentDay.plus({ days: 1 });
|
|
const wStart = dayStart.set({ hour: workStart.hour, minute: workStart.minute, second: 0, millisecond: 0 });
|
|
const wEnd = dayStart.set({ hour: workEnd.hour, minute: workEnd.minute, second: 0, millisecond: 0 });
|
|
const blocks = [];
|
|
if (dayStart < wStart) blocks.push(Interval.fromDateTimes(dayStart, wStart));
|
|
if (wEnd < dayEnd) blocks.push(Interval.fromDateTimes(wEnd, dayEnd));
|
|
for (const b of blocks) {
|
|
const i = b.intersection(searchInterval);
|
|
if (i?.isValid) nonWork.push(i);
|
|
}
|
|
currentDay = currentDay.plus({ days: 1 });
|
|
}
|
|
let allBlocked = [...busies, ...nonWork].sort((a, b) => a.start.valueOf() - b.start.valueOf());
|
|
const mergedBlocked = [];
|
|
for (const iv of allBlocked) {
|
|
const last = mergedBlocked.at(-1);
|
|
if (!last || !last.overlaps(iv)) {
|
|
mergedBlocked.push(iv);
|
|
} else {
|
|
mergedBlocked[mergedBlocked.length - 1] = last.union(iv);
|
|
}
|
|
}
|
|
const freePeriods = [];
|
|
if (mergedBlocked.length === 0) {
|
|
freePeriods.push(searchInterval);
|
|
} else {
|
|
if (searchStart < mergedBlocked[0].start) {
|
|
freePeriods.push(Interval.fromDateTimes(searchStart, mergedBlocked[0].start));
|
|
}
|
|
for (let i = 0; i < mergedBlocked.length - 1; i++) {
|
|
const gapStart = mergedBlocked[i].end;
|
|
const gapEnd = mergedBlocked[i + 1].start;
|
|
if (gapStart < gapEnd) {
|
|
freePeriods.push(Interval.fromDateTimes(gapStart, gapEnd));
|
|
}
|
|
}
|
|
if (mergedBlocked.at(-1).end < searchEnd) {
|
|
freePeriods.push(Interval.fromDateTimes(mergedBlocked.at(-1).end, searchEnd));
|
|
}
|
|
}
|
|
const availableSlots = [];
|
|
for (const freeIv of freePeriods) {
|
|
if (freeIv.length('milliseconds') < durationMinutes * 60 * 1000) continue;
|
|
let current = freeIv.start;
|
|
while (true) {
|
|
const slotEnd = current.plus({ minutes: durationMinutes });
|
|
if (slotEnd > freeIv.end) break;
|
|
availableSlots.push({
|
|
start: current.toISO(),
|
|
end: slotEnd.toISO()
|
|
});
|
|
current = slotEnd;
|
|
}
|
|
}
|
|
return availableSlots;
|
|
}
|
|
export default findAvailableSlots; |