import { AvailabilityComponent, DateRangeComponent, DateTimeRangeComponent, HourComponent, SlotAvailablity, SlotComponent, } from '@seed/interfaces/components.dates'; import _ from 'lodash'; import { DateTime, Interval } from 'luxon'; interface hoursRange { startHours: string; startHoursInNumber: number; endHours: string; endHoursInNumber: number; } /* breakTime: [ ['11:00', '14:00'], ['16:00', '18:00'], ], */ export function getNumberOfWeekendDays(dateBegin: DateTime, dateEnd: DateTime): number { let weekendDays = 0; let begin = dateBegin; while (begin <= dateEnd) { if (begin.weekday == 6 || begin.weekday == 7) weekendDays = weekendDays + 1; begin = begin.plus({ day: 1 }); } return weekendDays; } function isInTimeInterval(slotTime: DateTime, slotInterval: number, timeRange: hoursRange[]): boolean { const startTime = slotTime; const endTime = slotTime.plus({ minutes: slotInterval }); const startTimeInNumber = startTime.hour * 60 + startTime.minute; const endTimeInNumber = endTime.hour * 60 + endTime.minute; const isInInterval = timeRange.some((br) => { return startTimeInNumber < br.endHoursInNumber && endTimeInNumber > br.startHoursInNumber; // return (date.isSameOrAfter(startTime) && date.isSameOrBefore(endTime)) || (date.isSameOrAfter(startTime) && date.isSame(endTime)); }); return isInInterval; } export const getMinutesFromTimeInString = (str: string): number => { const timeArray = str.split(':'); return parseInt(timeArray[0]) * 60 + parseInt(timeArray[1]); }; export const getDateNoTZ = (old: Date): Date => { const userTimezoneOffset = old.getTimezoneOffset() * 60000; return new Date(old.getTime() - userTimezoneOffset); }; export const slotToDatesRange = (slot: SlotComponent): DateRangeComponent[] => { const dates: DateRangeComponent[] = []; const { startDate, endDate, startTime, endTime } = slot; const startTimes = startTime.split(':'); const endTimes = endTime.split(':'); let beginDate = DateTime.fromJSDate(startDate).startOf('day'); const stopDate = DateTime.fromJSDate(endDate).endOf('day'); while (beginDate < stopDate) { dates.push({ startDate: beginDate .set({ hour: parseInt(startTimes[0]) }) .set({ minute: parseInt(startTimes[1]) }) .toJSDate(), endDate: beginDate .set({ hour: parseInt(endTimes[0]) }) .set({ minute: parseInt(endTimes[1]) }) // Remove one seconds for overlapping .minus({ second: 1 }) .toJSDate(), }); beginDate = beginDate.plus({ day: 1 }); } return dates; }; export const getOneDaySlots = (input: { day: DateTime; dayUnavailability: DateTimeRangeComponent[]; slotInfo: string[][]; slotInterval: number; slotDuration: number; options?: { zone?: string; }; }): string[] => { const { day, dayUnavailability, slotInfo, slotDuration, slotInterval, options } = input; const zone = options?.zone || process.env.TIMEZONE || 'utc'; let currentDayMoment = day; // Check if currentDay = today const currentDayStart = currentDayMoment.toISODate(); const todayStart = DateTime.local().toISODate(); if (currentDayStart != todayStart) { currentDayMoment = day.startOf('day'); } const currentDayTimeInNumber = currentDayMoment.hour * 60 + currentDayMoment.minute; console.log('current day', currentDayMoment.toISO(), currentDayMoment.weekday); // Getting the slots for that day const daySlot = slotInfo[day.weekday - 1]; const currentDaySlots: { startTime: DateTime; endTime: DateTime; }[] = []; if (daySlot && daySlot.length > 0) { if (_.isArray(daySlot[0])) { _.each(daySlot, (d) => { currentDaySlots.push({ startTime: DateTime.fromFormat(d[0], 'HH:mm', { zone }), endTime: DateTime.fromFormat(d[1], 'HH:mm', { zone }), }); }); } else { currentDaySlots.push({ startTime: DateTime.fromFormat(daySlot[0], 'HH:mm', { zone }), endTime: DateTime.fromFormat(daySlot[1], 'HH:mm', { zone }), }); } } const thisDaysUnavailabilities: { startHours: string; startHoursInNumber: number; endHours: string; endHoursInNumber: number; }[] = getUnavailabilityOfThisDay(dayUnavailability, currentDayMoment); const currentDateSlots: string[] = []; console.log('thisDaysUnavailabilities', thisDaysUnavailabilities); currentDaySlots.forEach((element) => { let { startTime, endTime } = element; while (startTime < endTime) { if ( !(startTime.hour * 60 + startTime.minute < currentDayTimeInNumber) && !isInTimeInterval(startTime, slotDuration, thisDaysUnavailabilities) && startTime.hour * 60 + startTime.minute + slotDuration <= endTime.hour * 60 + endTime.minute ) { currentDateSlots.push(startTime.toFormat('HH:mm')); } startTime = startTime.plus({ minutes: slotInterval }); } }); return currentDateSlots; }; export const getMultipleDaysSlots = (input: { datesWanted: DateRangeComponent; datesUnavailable: DateTimeRangeComponent[]; slotInterval: number; slotInfo: string[][]; slotDuration: number; options?: { zone?: string; }; }): SlotAvailablity[] => { const returnSlots: SlotAvailablity[] = []; const { datesWanted, datesUnavailable, slotInterval, slotInfo, slotDuration, options } = input; const { startDate, endDate } = datesWanted; const zone = options?.zone || process.env.TIMEZONE || 'utc'; let beginDate = DateTime.fromJSDate(startDate, { zone }); const stopDate = DateTime.fromJSDate(endDate, { zone }); // Two loops // Looping into the wanted dates and keeping an index for the slotInfo while (beginDate < stopDate) { // Get the slot info from the array of array const slots = getOneDaySlots({ day: beginDate, dayUnavailability: datesUnavailable, slotInfo, slotInterval, slotDuration, options: { zone }, }); // Adding it to the array returnSlots.push({ date: beginDate.toJSDate(), slots, }); beginDate = beginDate.plus({ day: 1 }).startOf('day'); } return returnSlots; }; export function getUnavailabilityOfThisDay(dayUnavailability: DateTimeRangeComponent[], currentDayMoment: DateTime) { const thisDaysUnavailabilities: { startHours: string; startHoursInNumber: number; endHours: string; endHoursInNumber: number; }[] = []; dayUnavailability.forEach((element) => { const startDate = element.startDate; const endDate = element.endDate; // If there are some reservations in that day or in multiple days const isSameThanStartDate = currentDayMoment.hasSame(startDate, 'day'); const isSameThanEndDate = currentDayMoment.hasSame(endDate, 'day'); const startHours = startDate.toFormat('HH:mm'); const endHours = endDate.toFormat('HH:mm'); const starts = startHours.split(':'); const ends = endHours.split(':'); const startDateDay = startDate.startOf('day'); const endDateDay = startDate.endOf('day'); const startHoursInNumber = parseInt(starts[0]) * 60 + parseInt(starts[1]); const endHoursInNumber = parseInt(ends[0]) * 60 + parseInt(ends[1]); // If the reservation only last a day if (isSameThanStartDate && isSameThanEndDate) { thisDaysUnavailabilities.push({ startHours, endHours, startHoursInNumber, endHoursInNumber, }); } // If the reservation last multiple days else if (currentDayMoment >= startDateDay && currentDayMoment <= endDateDay) { // check if start date if (isSameThanStartDate) thisDaysUnavailabilities.push({ startHours, endHours: '23:59', startHoursInNumber, endHoursInNumber: 23 * 60 + 59, }); else if (isSameThanEndDate) thisDaysUnavailabilities.push({ startHours: '00:00', endHours, startHoursInNumber: 0, endHoursInNumber, }); else thisDaysUnavailabilities.push({ startHours: '00:00', endHours: '23:59', startHoursInNumber: 0, endHoursInNumber: 23 * 60 + 59, }); } }); return thisDaysUnavailabilities; } export function isTimeSlotInDates(dates: DateTimeRangeComponent[], startTime: string, endTime: string) { const startTimesInMinutes = getMinutesFromTimeInString(startTime); const endTimesInMinutes = getMinutesFromTimeInString(endTime); // Check for each date if the minutes are in it return dates.some((br) => { const brStartTimeInMinutes = br.startDate.hour * 60 + br.startDate.minute; const brEndTimeInMinutes = br.endDate.hour * 60 + br.endDate.minute; return startTimesInMinutes < brEndTimeInMinutes && endTimesInMinutes > brStartTimeInMinutes; }); } export function converDateRangeToDateTime( dates: DateRangeComponent[], options?: { zone?: string; }, ): DateTimeRangeComponent[] { const results: DateTimeRangeComponent[] = []; const zone = options?.zone || process.env.TIMEZONE || 'utc'; dates.forEach((element) => { results.push({ startDate: DateTime.fromJSDate(element.startDate, { zone }), endDate: DateTime.fromJSDate(element.endDate, { zone }), }); }); return results; }