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

9

13

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?

Flamingo answered 20/9, 2018 at 14:59 Comment(0)
R
1

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
Rancorous answered 31/10, 2023 at 4:34 Comment(0)
M
19

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;
}
Messmate answered 21/10, 2019 at 7:55 Comment(0)
R
11

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)
Rambouillet answered 20/9, 2018 at 15:4 Comment(6)
I will have to try it later, in cmd it says that it does not contain a package.json file and errors out.Flamingo
npm ERR! code ENOLOCAL npm ERR! Could not install from ">my directory<" as it does not contain a package.json file. E: I installed a couple other packages and they worked fineFlamingo
Check if you are in project directory, what machine you are using, Linux/Mac/Windows?Rambouillet
I am -- I installed a couple other packages after that error and they worked fineFlamingo
I'm using WindowsFlamingo
hit dir and see if you see package.json fileRambouillet
U
7

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).

Uzbek answered 21/9, 2018 at 6:47 Comment(2)
You say "not use external lib", and yet, both your examples use moment... Am I missing something?Assurance
The accepted solution uses moment-business-days vs just moment was my point.Uzbek
J
2
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");
};
Joaquinajoash answered 22/4, 2022 at 9:44 Comment(0)
D
1

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;
}
Disconnect answered 16/12, 2020 at 23:6 Comment(1)
if you input saturday, its goes wrongLycopodium
A
1
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')
}
Aten answered 11/6, 2021 at 11:33 Comment(0)
R
1

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
Rancorous answered 31/10, 2023 at 4:34 Comment(0)
S
0

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');
Sensual answered 18/12, 2019 at 15:48 Comment(2)
Unfortunately this code doesn't works. For example if on Monday 28/09/2020 I add 15 working days I'll end up to 17 Oct that is Saturday.Sandpit
Is this code faster? How? Shorter notated? Runtime wise? Someone who would read this in your code will not always understand what is going on here, so he needs time to think it out or being explained about. Also, how would I debug this when it has an unforeseen error? So with that in mind, I would not say this code is faster, merely that it would execute faster.Capone
P
0
// 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());
Pelaga answered 4/11, 2022 at 10:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.