'How to return the next available date

I'm building a project with express and I have a scheduling calendar. I want to give to my users next available day. Format YYYY-MM-DD.

Rules:

The next available day is usually tomorrow unless:
- After 4pm the next available day is two days from now (i.e. Monday afternoon they can book Wednesday);
- Friday after 4pm the next available day is Monday;
- For Saturday it's Monday;
- For Sunday it's Tuesday;

I also have an array of public holidays, which are also unavailable. If the next day is a public holiday, the app should return the day after.

When there is a public holiday my app goes into a loop and it runs the whole loop. I don't know how to fix this. I thought it would skip the loop when it runs the second time.

const publicHolidays = ['2018-09-28', '2018-12-25']

const availableDay = (nextDay) => {
  const d = new Date();
  const utc = d.getTime() + (d.getTimezoneOffset() * 60000);
  const nd = new Date(utc + (3600000 * 8));

  if (nextDay === undefined) {
    nextDay = 1;
  }
  if (nd.getDay() === 5 && nd.getHours() > 15) {
    nextDay = 3;
  } else if ([0, 6].includes(nd.getDay()) || nd.getHours() > 15) {
    nextDay = 2;
  }
  const day = new Date();
  const tomorrow = new Date(day);
  tomorrow.setDate(tomorrow.getDate() + nextDay);
  const yy = tomorrow.getFullYear();
  let mm = tomorrow.getMonth() + 1;
  if (mm < 10) {
    mm = `0${mm}`;
  }
  let dd = tomorrow.getDate();
  if (dd < 10) {
    dd = `0${dd}`;
  }
  const available = `${yy}-${mm}-${dd}`;
  if (publicHolidays.includes(available)) {
    const nextDay = 7;
    for (let i = 2; i < nextDay; i += 1) {
      availableDay(i);
    }
  } else {
    console.log('returning available', available);
    return(available);
  }
}

availableDay()


Solution 1:[1]

I think this logic will work - I've created a function to do the "date string - yyyy-mm-dd" thing because it's used in two places now

I also check for weekends by tomorrow.getDay() % 6 === 0 - you can of course use [0, 6].includes(tomorrow.getDay()) if you prefer

const publicHolidays = ['2018-09-28', '2018-12-25']

const availableDay = () => {
    let nextDay = 1; // since we are not recursive any more
    const d = new Date();
    const utc = d.getTime() + (d.getTimezoneOffset() * 60000);
    const nd = new Date(utc + (3600000 * 8));

    if (nd.getDay() === 5 && nd.getHours() > 15) {
        nextDay = 3;
    } else if ([0, 6].includes(nd.getDay()) || nd.getHours() > 15) {
        nextDay = 2;
    }
    const day = new Date();
    const tomorrow = new Date(day);
    tomorrow.setDate(tomorrow.getDate() + nextDay);
    // changes start here
    const dateString = d => `${.getFullYear()}-${('0' + (d.getMonth() + 1)).toString(-2)}-${('0' + d.getDate()).toString(-2)}`;

    let available = dateString(tomorrow);
    while (publicHolidays.includes(available) || (tomorrow.getDay() === 0)) {
        tomorrow.setDate(tomorrow.getDate() + 1);
        available = dateString(tomorrow);
    }
    console.log('returning available', available);
    return(available);
}

availableDay()

There's probably more you can do to streamline the code - but this should fix the problem at least

Solution 2:[2]

I think you should always + 1 to nextDay. so if today is public holiday, try get the next day. the cycle repeat until it is not public holiday.

if (publicHolidays.includes(available)) {
    availableDay(nextDay +1 );
} else {
    console.log('returning available', available);
    return(available);
}

Solution 3:[3]

Here is a more generic solution that might be applicable for people searching for something similar:

/**
 * @summary Finds the next available date between a range, excluding a list of unavailable dates
 * @param {Date}          startDate    The beginning of the date range.
 * @param {Date}          endDate      The beginning of the date range.
 * @param {Array of Date} excludeDates Dates that are not available.
 */
export const findNextAvailableDate = (startDate, endDate, excludeDates) => {
  const excludeDatesStrArr = excludeDates.map(date => {
    // Make sure dates are in a consistent string format so we can check for equality
    excludeDate.setUTCHours(0, 0, 0, 0)
    return excludeDate.toISOString()
  })
  let possibleDate = startDate
  possibleDate.setUTCHours(0, 0, 0, 0)
  let possibleDateStr = possibleDate.toISOString()
  while (possibleDateStr !== endDate) {
    if (!excludeDatesStrArr.includes(possibleDateStr)) {
      // Date is not in exclude array, return available date
      return possibleDate
    } else {
      // Date is included in exclude array, iterate to the next day
      const newDate = possibleDate.setDate(possibleDate.getDate() + 1)
      possibleDate = new Date(newDate)
      possibleDate.setUTCHours(0, 0, 0, 0)
      possibleDateStr = possibleDate.toISOString()
    }
  }
  // Did not find next available date
  return false
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 shadow
Solution 3 Allan of Sydney