calcom/lib/slots.ts
2021-06-27 22:30:11 +00:00

109 lines
3.5 KiB
TypeScript

import dayjs, {Dayjs} from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
interface GetSlotsType {
inviteeDate: Dayjs;
frequency: number;
workingHours: [];
minimumBookingNotice?: number;
}
interface Boundary {
lowerBound: number;
upperBound: number;
}
const freqApply: number = (cb, value: number, frequency: number): number => cb(value / frequency) * frequency;
const intersectBoundary = (a: Boundary, b: Boundary) => {
if (
a.upperBound < b.lowerBound || a.lowerBound > b.upperBound
) {
return;
}
return {
lowerBound: Math.max(b.lowerBound, a.lowerBound),
upperBound: Math.min(b.upperBound, a.upperBound)
};
}
// say invitee is -60,1380, and boundary is -120,240 - the overlap is -60,240
const getOverlaps = (inviteeBoundary: Boundary, boundaries: Boundary[]) => boundaries
.map(
(boundary) => intersectBoundary(inviteeBoundary, boundary)
).filter(Boolean);
const organizerBoundaries = (workingHours: [], inviteeDate: Dayjs, inviteeBounds: Boundary): Boundary[] => {
const boundaries: Boundary[] = [];
const startDay: number = +inviteeDate.utc().startOf('day').add(inviteeBounds.lowerBound, 'minutes').format('d');
const endDay: number = +inviteeDate.utc().startOf('day').add(inviteeBounds.upperBound, 'minutes').format('d');
workingHours.forEach( (item) => {
const lowerBound: number = item.startTime;
const upperBound: number = lowerBound + item.length;
if (startDay !== endDay) {
if (inviteeBounds.lowerBound < 0) {
// lowerBound edges into the previous day
if (item.days.includes(startDay)) {
boundaries.push({ lowerBound: lowerBound - 1440, upperBound: upperBound - 1440 });
}
if (item.days.includes(endDay)) {
boundaries.push({ lowerBound, upperBound });
}
} else {
// upperBound edges into the next day
if (item.days.includes(endDay)) {
boundaries.push({ lowerBound: lowerBound + 1440, upperBound: upperBound + 1440 });
}
if (item.days.includes(startDay)) {
boundaries.push({ lowerBound, upperBound });
}
}
} else {
boundaries.push({lowerBound, upperBound});
}
});
return boundaries;
};
const inviteeBoundary = (startTime: number, utcOffset: number, frequency: number): Boundary => {
const upperBound: number = freqApply(Math.floor, 1440 - utcOffset, frequency);
const lowerBound: number = freqApply(Math.ceil, startTime - utcOffset, frequency);
return {
lowerBound, upperBound,
};
};
const getSlotsBetweenBoundary = (frequency: number, {lowerBound,upperBound}: Boundary) => {
const slots: Dayjs[] = [];
for (
let minutes = 0;
lowerBound + minutes <= upperBound - frequency;
minutes += frequency
) {
slots.push(<Dayjs>dayjs.utc().startOf('day').add(lowerBound + minutes, 'minutes'));
}
return slots;
};
const getSlots = (
{ inviteeDate, frequency, minimumBookingNotice, workingHours, }: GetSlotsType
): Dayjs[] => {
const startTime = (
dayjs.utc().isSame(dayjs(inviteeDate), 'day') ? inviteeDate.hour() * 60 + inviteeDate.minute() + minimumBookingNotice : 0
);
const inviteeBounds = inviteeBoundary(startTime, inviteeDate.utcOffset(), frequency);
return getOverlaps(
inviteeBounds, organizerBoundaries(workingHours, inviteeDate, inviteeBounds)
).reduce(
(slots, boundary: Boundary) => [...slots, ...getSlotsBetweenBoundary(frequency, boundary) ], []
).map(
(slot) => slot.utcOffset(dayjs(inviteeDate).utcOffset())
)
}
export default getSlots;