mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-01-14 08:37:56 +00:00
85 lines
2.5 KiB
JavaScript
85 lines
2.5 KiB
JavaScript
const findAvailableSlots = async (cal1, cal2, constraints) => {
|
|
const { DateTime, Interval } = await import('https://cdn.skypack.dev/luxon');
|
|
|
|
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;
|
|
} else {
|
|
const nextBusy = allBusy.find(b => b.end > cursor && b.start <= cursor);
|
|
cursor = nextBusy ? nextBusy.end : cursor.plus({ minutes: 1 });
|
|
}
|
|
}
|
|
|
|
return slots;
|
|
};
|
|
export default findAvailableSlots; |