mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-01-13 16:17:54 +00:00
77 lines
2.8 KiB
JavaScript
77 lines
2.8 KiB
JavaScript
async function findAvailableSlots(calendar1, calendar2, constraints) {
|
|
const { DateTime, Interval } = await import('https://cdn.jsdelivr.net/npm/luxon@3.4.4/+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 busyIntervals = [...calendar1, ...calendar2]
|
|
.map(b => Interval.fromDateTimes(
|
|
DateTime.fromISO(b.start, { zone: 'utc' }),
|
|
DateTime.fromISO(b.end, { zone: 'utc' })
|
|
))
|
|
.filter(i => i.isValid)
|
|
.sort((a, b) => a.start - b.start);
|
|
|
|
const merged = [];
|
|
for (const interval of busyIntervals) {
|
|
if (!merged.length || merged[merged.length - 1].end < interval.start) {
|
|
merged.push(interval);
|
|
} else {
|
|
const last = merged.pop();
|
|
merged.push(Interval.fromDateTimes(
|
|
last.start,
|
|
last.end > interval.end ? last.end : interval.end
|
|
));
|
|
}
|
|
}
|
|
|
|
const freeIntervals = [];
|
|
let cursor = rangeStart;
|
|
for (const busy of merged) {
|
|
if (busy.start > cursor && busy.start <= rangeEnd) {
|
|
freeIntervals.push(Interval.fromDateTimes(cursor, busy.start < rangeEnd ? busy.start : rangeEnd));
|
|
}
|
|
cursor = busy.end > cursor ? busy.end : cursor;
|
|
}
|
|
if (cursor < rangeEnd) {
|
|
freeIntervals.push(Interval.fromDateTimes(cursor, rangeEnd));
|
|
}
|
|
|
|
const getWorkInterval = day => {
|
|
const start = day.set({ hour: whStartH, minute: whStartM, second: 0, millisecond: 0 });
|
|
const end = day.set({ hour: whEndH, minute: whEndM, second: 0, millisecond: 0 });
|
|
return start < end ? Interval.fromDateTimes(start, end) : null;
|
|
};
|
|
|
|
const slots = [];
|
|
|
|
for (const free of freeIntervals) {
|
|
let day = free.start.startOf('day');
|
|
const endDay = free.end.startOf('day');
|
|
|
|
while (day <= endDay) {
|
|
const workInt = getWorkInterval(day);
|
|
if (workInt) {
|
|
const intersection = free.intersection(workInt);
|
|
if (intersection && intersection.length('minutes') >= durationMinutes) {
|
|
let slotStart = intersection.start;
|
|
while (slotStart.plus({ minutes: durationMinutes }) <= intersection.end) {
|
|
const slotEnd = slotStart.plus({ minutes: durationMinutes });
|
|
slots.push({
|
|
start: slotStart.toISO({ suppressMilliseconds: true }),
|
|
end: slotEnd.toISO({ suppressMilliseconds: true })
|
|
});
|
|
slotStart = slotEnd;
|
|
}
|
|
}
|
|
}
|
|
day = day.plus({ days: 1 });
|
|
}
|
|
}
|
|
|
|
return slots.sort((a, b) => a.start.localeCompare(b.start));
|
|
}
|
|
export default findAvailableSlots; |