mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-03-17 03:11:01 +00:00
105 lines
3.2 KiB
JavaScript
105 lines
3.2 KiB
JavaScript
export async function findAvailableSlots(calendarA, calendarB, constraints) {
|
|
const { DateTime, Interval } = await import('https://cdn.jsdelivr.net/npm/luxon@3/+esm')
|
|
const { durationMinutes, searchRange, workHours } = constraints
|
|
const toUtc = v => DateTime.fromISO(v, { zone: 'utc' })
|
|
const pad = v => `${v}`.padStart(2, '0')
|
|
const [whs, wms] = workHours.start.split(':').map(Number)
|
|
const [whe, wme] = workHours.end.split(':').map(Number)
|
|
const rangeStart = toUtc(searchRange.start)
|
|
const rangeEnd = toUtc(searchRange.end)
|
|
const duration = { minutes: durationMinutes }
|
|
const stepMs = durationMinutes * 6e4
|
|
|
|
if (
|
|
!rangeStart.isValid ||
|
|
!rangeEnd.isValid ||
|
|
!Number.isFinite(durationMinutes) ||
|
|
durationMinutes <= 0 ||
|
|
rangeEnd <= rangeStart ||
|
|
[whs, wms, whe, wme].some(v => !Number.isInteger(v))
|
|
) return []
|
|
|
|
const clip = ({ start, end }) => {
|
|
const s = toUtc(start)
|
|
const e = toUtc(end)
|
|
if (!s.isValid || !e.isValid || e <= s) return null
|
|
const a = s < rangeStart ? rangeStart : s
|
|
const b = e > rangeEnd ? rangeEnd : e
|
|
return b > a ? { start: a, end: b } : null
|
|
}
|
|
|
|
const busy = [...calendarA, ...calendarB]
|
|
.map(clip)
|
|
.filter(Boolean)
|
|
.sort((a, b) => a.start.toMillis() - b.start.toMillis())
|
|
|
|
const merged = []
|
|
for (const slot of busy) {
|
|
const last = merged[merged.length - 1]
|
|
if (!last || slot.start > last.end) merged.push({ ...slot })
|
|
else if (slot.end > last.end) last.end = slot.end
|
|
}
|
|
|
|
const free = []
|
|
let cursor = rangeStart
|
|
for (const slot of merged) {
|
|
if (slot.start > cursor) free.push({ start: cursor, end: slot.start })
|
|
if (slot.end > cursor) cursor = slot.end
|
|
}
|
|
if (cursor < rangeEnd) free.push({ start: cursor, end: rangeEnd })
|
|
|
|
const dayWindows = []
|
|
for (
|
|
let d = rangeStart.startOf('day');
|
|
d < rangeEnd;
|
|
d = d.plus({ days: 1 })
|
|
) {
|
|
const ws = d.set({
|
|
hour: whs,
|
|
minute: wms,
|
|
second: 0,
|
|
millisecond: 0
|
|
})
|
|
const we = d.set({
|
|
hour: whe,
|
|
minute: wme,
|
|
second: 0,
|
|
millisecond: 0
|
|
})
|
|
if (we <= ws) continue
|
|
const start = ws < rangeStart ? rangeStart : ws
|
|
const end = we > rangeEnd ? rangeEnd : we
|
|
if (end > start) dayWindows.push({ start, end })
|
|
}
|
|
|
|
const results = []
|
|
let wi = 0
|
|
|
|
for (const f of free) {
|
|
while (wi < dayWindows.length && dayWindows[wi].end <= f.start) wi++
|
|
for (let i = wi; i < dayWindows.length; i++) {
|
|
const w = dayWindows[i]
|
|
if (w.start >= f.end) break
|
|
const start = f.start > w.start ? f.start : w.start
|
|
const end = f.end < w.end ? f.end : w.end
|
|
if (end <= start) continue
|
|
const ms = end.toMillis() - start.toMillis()
|
|
const count = Math.floor(ms / stepMs)
|
|
for (let j = 0; j < count; j++) {
|
|
const s = start.plus({ minutes: j * durationMinutes })
|
|
const e = s.plus(duration)
|
|
if (e <= end && Interval.fromDateTimes(s, e).length('minutes') === durationMinutes) {
|
|
results.push({
|
|
start: s.toUTC().toISO({ suppressMilliseconds: true }),
|
|
end: e.toUTC().toISO({ suppressMilliseconds: true })
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
export default findAvailableSlots;
|
|
// Generation time: 9.884s
|
|
// Result: PASS
|