Micah C
Micah C

Reputation: 132

How can I use moment.js to add days, excluding weekends?

I'm setting a default follow-up date two days from current date, which currently works:

const Notify = moment().add(2, 'days').toDate();

However, I would like to exclude weekends. So I installed moment WeekDay, but I can't seem to get it to work with adding days to the current date. The documentation calls for:

moment().weekday(0)

But I can't get that to work with adding in two days forward. Any ideas?

Upvotes: 13

Views: 28590

Answers (9)

Nathan Smith
Nathan Smith

Reputation: 44

The highest-rated solution is less verbose, but uses a loop to add the days one at a time, rather than calculating the number of calendar days to add ahead of time.

Other solutions that have been posted try to calculate, but all of them either do not work, or have edge cases they don't handle (e.g., what if the original date happens to fall on a weekend?)

The suggestion to use moment-business-days is good if you want to handle holidays across different locales and leverage other features of the library, but (IMO) it is overkill if we are just sticking to what OP asks for, which is "add X days, skipping Saturdays and Sundays."

Anyway, I had to do this on a project recently, and this is the solution I came up with:

const addBusinessDaysToDate = (date, businessDays) => {
    // bit of type checking, and making sure not to mutate inputs :: 
    const momentDate = date instanceof moment ? date.clone() : moment(date);

    if (!Number.isSafeInteger(businessDays) || businessDays <= 0) {
        // handle these situations as appropriate for your program; here I'm just returning the moment instance :: 
        return momentDate;
    } else {
        // for each full set of five business days, we know we want to add 7 calendar days :: 
        const calendarDaysToAdd = Math.floor(businessDays / 5) * 7;
        momentDate.add(calendarDaysToAdd, "days");
        
        // ...and we calculate the additional business days that didn't fit neatly into groups of five :: 
        const remainingDays = businessDays % 5;
        
        // if the date is currently on a weekend, we need to adjust it back to the most recent Friday :: 
        const dayOfWeekNumber = momentDate.day();
        if (dayOfWeekNumber === 6) {
            // Saturday -- subtract one day :: 
            momentDate.subtract(1, "days"); 
        } else if (dayOfWeekNumber === 0) {
            // Sunday -- subtract two days :: 
            momentDate.subtract(2, "days");
        }

        // now we need to deal with any of the remaining days calculated above :: 
        if ((momentDate.day() + remainingDays) > 5) {
            // this means that adding the remaining days has caused us to hit another weekend; 
            // we must account for this by adding two extra calendar days :: 
            return momentDate.add(remainingDays + 2, "days");
        } else {
            // we can just add the remaining days :: 
            return momentDate.add(remainingDays, "days");
        }
    }
};

And here's the result of a quick little test script:

_________________________________________
Original Date ::  2023-10-28
Plus  3  Business Days ::  2023-11-01
Plus  10  Business Days ::  2023-11-10
Plus  14  Business Days ::  2023-11-16
Plus  15  Business Days ::  2023-11-17
Plus  22  Business Days ::  2023-11-28

_________________________________________
Original Date ::  2023-10-29
Plus  3  Business Days ::  2023-11-01
Plus  10  Business Days ::  2023-11-10
Plus  14  Business Days ::  2023-11-16
Plus  15  Business Days ::  2023-11-17
Plus  22  Business Days ::  2023-11-28

_________________________________________
Original Date ::  2023-10-30
Plus  3  Business Days ::  2023-11-02
Plus  10  Business Days ::  2023-11-13
Plus  14  Business Days ::  2023-11-17
Plus  15  Business Days ::  2023-11-20
Plus  22  Business Days ::  2023-11-29

_________________________________________
Original Date ::  2023-10-31
Plus  3  Business Days ::  2023-11-03
Plus  10  Business Days ::  2023-11-14
Plus  14  Business Days ::  2023-11-20
Plus  15  Business Days ::  2023-11-21
Plus  22  Business Days ::  2023-11-30

_________________________________________
Original Date ::  2023-11-01
Plus  3  Business Days ::  2023-11-06
Plus  10  Business Days ::  2023-11-15
Plus  14  Business Days ::  2023-11-21
Plus  15  Business Days ::  2023-11-22
Plus  22  Business Days ::  2023-12-01

_________________________________________
Original Date ::  2023-11-02
Plus  3  Business Days ::  2023-11-07
Plus  10  Business Days ::  2023-11-16
Plus  14  Business Days ::  2023-11-22
Plus  15  Business Days ::  2023-11-23
Plus  22  Business Days ::  2023-12-04

_________________________________________
Original Date ::  2023-11-03
Plus  3  Business Days ::  2023-11-08
Plus  10  Business Days ::  2023-11-17
Plus  14  Business Days ::  2023-11-23
Plus  15  Business Days ::  2023-11-24
Plus  22  Business Days ::  2023-12-05

Upvotes: 1

Banuka Vidusanka
Banuka Vidusanka

Reputation: 1

// using pure JS

function addBusinessDays(originalDate, numDaysToAdd) {
  const Sunday = 0;
  const Saturday = 6;
  let daysRemaining = numDaysToAdd;

  const newDate = originalDate;

  while (daysRemaining > 0) {
   newDate.setDate(newDate.getDate() + 1);
    if (newDate.getDay() !== 0 && newDate.getDay() !== 6) {
    // skip sunday & saturday
      daysRemaining--;
    }
  }

  return newDate;
}

var dt = new Date(); // get date
var business_days = 8;

newDate = addBusinessDays(dt, business_days);


console.log(newDate.toString());

Upvotes: 0

mouchin777
mouchin777

Reputation: 1588

const addWorkingDays = (date: Moment, days: number) => {
  let newDate = date.clone();
  for (let i = 0; i < days; i++) {
    if (newDate.isoWeekday() !== 6 && newDate.isoWeekday() !== 7) {
      newDate = newDate.add(1, "days");
    } else {
      newDate = newDate.add(1, "days");
      i--;
    }
  }
  return newDate.format("YYYY/MM/DD");
};

Upvotes: 2

RH-indra Poudel
RH-indra Poudel

Reputation: 31

var moment = require("moment")
function addWorkingDay(date, days){
    let daysToAdd = days
    const today = moment(date);
    const nextWeekStart = today.clone().add(1, 'week').weekday(1);
    const weekEnd = today.clone().weekday(5);

    const daysTillWeekEnd = Math.max(0, weekEnd.diff(today, 'days'));
    if(daysTillWeekEnd >= daysToAdd) return today.clone().add(daysToAdd, 'days');
    
    daysToAdd = daysToAdd - daysTillWeekEnd - 1;
    
    return nextWeekStart.add(Math.floor(daysToAdd/5), 'week').add(daysToAdd % 5, 'days')
}

Upvotes: 1

Ryan Loggerythm
Ryan Loggerythm

Reputation: 3314

This will do it based on any starting date, and without a costly loop. You calculate the number of weekend days you need to skip over, then just offset by the number of weekdays and weekends, together.

function addWeekdays(year, month, day, numberOfWeekdays) {
    var originalDate = year + '-' + month + '-' + day;
    var futureDate = moment(originalDate);
    var currentDayOfWeek = futureDate.day();            // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
    var numberOfWeekends = Math.floor((currentDayOfWeek + numberOfWeekdays - 1) / 5);   // calculate the number of weekends to skip over

    futureDate.add(numberOfWeekdays + numberOfWeekends * 2, 'days');    // account for the 2 days per weekend

    return futureDate;
}

Upvotes: 1

Eri Indrawan
Eri Indrawan

Reputation: 94

I think this code will be faster:

var businessDays = 10;
var days = businessDays + Math.floor((Math.min(moment().day(),5)+businessDays)/6)*2;
moment.add(days, 'days');

Upvotes: 0

zeckdude
zeckdude

Reputation: 16163

This solution is simple, easy to follow, and works well for me:

function addBusinessDays(originalDate, numDaysToAdd) {
  const Sunday = 0;
  const Saturday = 6;
  let daysRemaining = numDaysToAdd;

  const newDate = originalDate.clone();

  while (daysRemaining > 0) {
    newDate.add(1, 'days');
    if (newDate.day() !== Sunday && newDate.day() !== Saturday) {
      daysRemaining--;
    }
  }

  return newDate;
}

Upvotes: 19

Akrion
Akrion

Reputation: 18515

You could also not use external lib and do a simple function like one of these two:

const WEEKEND = [moment().day("Saturday").weekday(), moment().day("Sunday").weekday()]

const addBusinessDays1 = (date, daysToAdd) => {
  var daysAdded = 0,
    momentDate = moment(new Date(date));
  while (daysAdded < daysToAdd) {
    momentDate = momentDate.add(1, 'days');
    if (!WEEKEND.includes(momentDate.weekday())) {
      daysAdded++
    }
  }

  return momentDate;
}
console.log(addBusinessDays1(new Date(), 7).format('MM/DD/YYYY'))
console.log(addBusinessDays1('09-20-2018', 3).format('MM/DD/YYYY'))

// This is the somewhat faster version
const addBusinessDays2 = (date, days) => {
  var d = moment(new Date(date)).add(Math.floor(days / 5) * 7, 'd');
  var remaining = days % 5;
  while (remaining) {
    d.add(1, 'd');
    if (d.day() !== 0 && d.day() !== 6)
      remaining--;
  }
  return d;
};

console.log(addBusinessDays2(new Date(), 7).format('MM/DD/YYYY'))
console.log(addBusinessDays2('09-20-2018', 3).format('MM/DD/YYYY'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>

They are slightly modified from this post and I think are a good alternative to external library you have to carry/deal with (assuming this is the only part you need and not other features of that lib).

Upvotes: 7

Harshal Yeole
Harshal Yeole

Reputation: 4983

Try: moment-business-days

It should help you.

Example:

var momentBusinessDays = require("moment-business-days")

momentBusinessDays('20-09-2018', 'DD-MM-YYYY').businessAdd(3)._d 

Result:

Tue Sep 25 2018 00:00:00 GMT+0530 (IST)

Upvotes: 11

Related Questions