mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-03-17 11:21:00 +00:00
106 lines
3.4 KiB
JavaScript
106 lines
3.4 KiB
JavaScript
async function findAvailableSlots(calendar1, calendar2, constraints) {
|
|
const { default: dayjs } = await import("https://cdn.skypack.dev/dayjs@1.11.10");
|
|
const { default: utc } = await import("https://cdn.skypack.dev/dayjs@1.11.10/plugin/utc");
|
|
const { default: customParseFormat } = await import("https://cdn.skypack.dev/dayjs@1.11.10/plugin/customParseFormat");
|
|
|
|
dayjs.extend(utc);
|
|
dayjs.extend(customParseFormat);
|
|
|
|
const { durationMinutes, searchRange, workHours } = constraints;
|
|
const d = (iso) => dayjs.utc(iso);
|
|
const durationMs = durationMinutes * 60 * 1000;
|
|
|
|
const mergedBusy = mergeBusySlots(
|
|
[...calendar1, ...calendar2].map((s) => ({ start: d(s.start), end: d(s.end) }))
|
|
);
|
|
|
|
const rangeStart = d(searchRange.start);
|
|
const rangeEnd = d(searchRange.end);
|
|
|
|
const freeSlots = extractFreeSlots(mergedBusy, rangeStart, rangeEnd);
|
|
|
|
return freeSlots.flatMap((free) =>
|
|
generateSlots(free.start, free.end, durationMs, workHours, dayjs)
|
|
);
|
|
}
|
|
|
|
function mergeBusySlots(slots) {
|
|
if (!slots.length) return [];
|
|
|
|
const sorted = slots.slice().sort((a, b) => a.start.valueOf() - b.start.valueOf());
|
|
|
|
return sorted.reduce((merged, curr) => {
|
|
if (!merged.length) return [{ ...curr }];
|
|
const last = merged[merged.length - 1];
|
|
if (curr.start.valueOf() <= last.end.valueOf()) {
|
|
last.end = curr.end.valueOf() > last.end.valueOf() ? curr.end : last.end;
|
|
} else {
|
|
merged.push({ ...curr });
|
|
}
|
|
return merged;
|
|
}, []);
|
|
}
|
|
|
|
function extractFreeSlots(busySlots, rangeStart, rangeEnd) {
|
|
const free = [];
|
|
let cursor = rangeStart;
|
|
|
|
for (const busy of busySlots) {
|
|
if (busy.start.valueOf() > cursor.valueOf()) {
|
|
const freeEnd = busy.start.valueOf() < rangeEnd.valueOf() ? busy.start : rangeEnd;
|
|
if (freeEnd.valueOf() > cursor.valueOf()) {
|
|
free.push({ start: cursor, end: freeEnd });
|
|
}
|
|
}
|
|
if (busy.end.valueOf() > cursor.valueOf()) {
|
|
cursor = busy.end;
|
|
}
|
|
if (cursor.valueOf() >= rangeEnd.valueOf()) break;
|
|
}
|
|
|
|
if (cursor.valueOf() < rangeEnd.valueOf()) {
|
|
free.push({ start: cursor, end: rangeEnd });
|
|
}
|
|
|
|
return free;
|
|
}
|
|
|
|
function generateSlots(freeStart, freeEnd, durationMs, workHours, dayjs) {
|
|
const [whStartH, whStartM] = workHours.start.split(":").map(Number);
|
|
const [whEndH, whEndM] = workHours.end.split(":").map(Number);
|
|
const slots = [];
|
|
|
|
let dayDate = freeStart.utc().startOf("day");
|
|
const lastDay = freeEnd.utc().startOf("day");
|
|
|
|
while (dayDate.valueOf() <= lastDay.valueOf()) {
|
|
const wStart = dayDate.hour(whStartH).minute(whStartM).second(0).millisecond(0);
|
|
const wEnd = dayDate.hour(whEndH).minute(whEndM).second(0).millisecond(0);
|
|
|
|
const slotStart = Math.max(freeStart.valueOf(), wStart.valueOf());
|
|
const slotEnd = Math.min(freeEnd.valueOf(), wEnd.valueOf());
|
|
|
|
let cursor = alignToSlot(slotStart, wStart.valueOf(), durationMs);
|
|
|
|
while (cursor + durationMs <= slotEnd) {
|
|
slots.push({
|
|
start: dayjs.utc(cursor).toISOString(),
|
|
end: dayjs.utc(cursor + durationMs).toISOString(),
|
|
});
|
|
cursor += durationMs;
|
|
}
|
|
|
|
dayDate = dayDate.add(1, "day");
|
|
}
|
|
|
|
return slots;
|
|
}
|
|
|
|
function alignToSlot(ts, workDayStart, durationMs) {
|
|
const offset = ts - workDayStart;
|
|
const remainder = offset % durationMs;
|
|
return remainder === 0 ? ts : ts + (durationMs - remainder);
|
|
}
|
|
export default findAvailableSlots;
|
|
// Generation time: 13.592s
|
|
// Result: FAIL
|