18

I have a javascript function which is calculating working days between 2 dates, it works, but the problem is that it not consider holidays. How can I modify this function, for example by adding holidays in exception array?

Searched in internet about this question, but haven't find about holidays exception.

For example holidays array:

var holidays = ['2016-05-03','2016-05-05'];

And I have a functions to calculate this:

function workingDaysBetweenDates(d0, d1) {
    var startDate = parseDate(d0);
    var endDate = parseDate(d1);  
    // Validate input
    if (endDate < startDate)
        return 0;

    // Calculate days between dates
    var millisecondsPerDay = 86400 * 1000; // Day in milliseconds
    startDate.setHours(0,0,0,1);  // Start just after midnight
    endDate.setHours(23,59,59,999);  // End just before midnight
    var diff = endDate - startDate;  // Milliseconds between datetime objects    
    var days = Math.ceil(diff / millisecondsPerDay);

    // Subtract two weekend days for every week in between
    var weeks = Math.floor(days / 7);
    days = days - (weeks * 2);

    // Handle special cases
    var startDay = startDate.getDay();
    var endDay = endDate.getDay();

    // Remove weekend not previously removed.   
    if (startDay - endDay > 1)         
        days = days - 2;      

    // Remove start day if span starts on Sunday but ends before Saturday
    if (startDay == 0 && endDay != 6)
        days = days - 1  

    // Remove end day if span ends on Saturday but starts after Sunday
    if (endDay == 6 && startDay != 0)
        days = days - 1  

    return days;
}
function parseDate(input) {
    // Transform date from text to date
  var parts = input.match(/(\d+)/g);
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}

Have made an example in jsfiddle:

JSFiddle example

Maybe there are some other functions which can easy use in Jquery?

AlexIL
  • 523
  • 3
  • 8
  • 23

10 Answers10

33

Try:

var startDate = new Date('05/03/2016');
var endDate = new Date('05/10/2016');
var numOfDates = getBusinessDatesCount(startDate,endDate);

function getBusinessDatesCount(startDate, endDate) {
    let count = 0;
    const curDate = new Date(startDate.getTime());
    while (curDate <= endDate) {
        const dayOfWeek = curDate.getDay();
        if(dayOfWeek !== 0 && dayOfWeek !== 6) count++;
        curDate.setDate(curDate.getDate() + 1);
    }
    alert(count);
    return count;
}
AKA
  • 5,479
  • 4
  • 22
  • 36
Dhara Parmar
  • 8,021
  • 1
  • 16
  • 27
  • But how can I integrate there holidays count? – AlexIL May 06 '16 at 09:53
  • I know @dhara-parmar's answer doesn't completely answer the original question (because it doesn't account for holidays), but as a quick and easy utility for counting working days, it's exactly what I needed. – Lynn Hoffman Apr 22 '20 at 13:29
  • 3
    This worked for me but the startDate gets modified in the function. I resolved this by doing the following: `var curDate = new Date(startDate.getTime());` – Aaron Oct 16 '20 at 00:09
20

The easiest way to achieve it is looking for these days between your begin and end date.

Edit: I added an additional verification to make sure that only working days from holidays array are subtracted.

$(document).ready(() => {
  $('#calc').click(() => {
  var d1 = $('#d1').val();
  var d2 = $('#d2').val();
    $('#dif').text(workingDaysBetweenDates(d1,d2));
  });
});

let workingDaysBetweenDates = (d0, d1) => {
  /* Two working days and an sunday (not working day) */
  var holidays = ['2016-05-03', '2016-05-05', '2016-05-07'];
  var startDate = parseDate(d0);
  var endDate = parseDate(d1);  

// Validate input
  if (endDate <= startDate) {
    return 0;
  }

// Calculate days between dates
  var millisecondsPerDay = 86400 * 1000; // Day in milliseconds
  startDate.setHours(0, 0, 0, 1);  // Start just after midnight
  endDate.setHours(23, 59, 59, 999);  // End just before midnight
  var diff = endDate - startDate;  // Milliseconds between datetime objects    
  var days = Math.ceil(diff / millisecondsPerDay);

  // Subtract two weekend days for every week in between
  var weeks = Math.floor(days / 7);
  days -= weeks * 2;

  // Handle special cases
  var startDay = startDate.getDay();
  var endDay = endDate.getDay();
    
  // Remove weekend not previously removed.   
  if (startDay - endDay > 1) {
    days -= 2;
  }
  // Remove start day if span starts on Sunday but ends before Saturday
  if (startDay == 0 && endDay != 6) {
    days--;  
  }
  // Remove end day if span ends on Saturday but starts after Sunday
  if (endDay == 6 && startDay != 0) {
    days--;
  }
  /* Here is the code */
  holidays.forEach(day => {
    if ((day >= d0) && (day <= d1)) {
      /* If it is not saturday (6) or sunday (0), substract it */
      if ((parseDate(day).getDay() % 6) != 0) {
        days--;
      }
    }
  });
  return days;
}
           
function parseDate(input) {
    // Transform date from text to date
  var parts = input.match(/(\d+)/g);
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="d1" value="2016-05-02"><br>
<input type="text" id="d2" value="2016-05-08">

<p>Working days count: <span id="dif"></span></p>
<button id="calc">Calc</button>

<p>
Now it shows 5 days, but I need for example add holidays 
3 and 5 May (2016-05-03 and 2016-05-05) so the result will be 3 working days
</p>
OscarGarcia
  • 1,995
  • 16
  • 17
  • 1
    Sorry, I tryed to reuse function parameters but I can't, so I used `$('#d1').val()` instead `parseDate(d0)`. – OscarGarcia May 06 '16 at 10:06
  • Thank you! I have made changes in your answer. Now all works correct :) – AlexIL May 06 '16 at 10:13
  • You're welcome, I improved it too using `days--` instead `days = days - 1`. I don't know why when I did that same changes the code showed me "5 days" :) EDIT: Ahhh! parseDate returns a Date, not a string! :D – OscarGarcia May 06 '16 at 10:19
  • 2
    This is perfect except for when the two dates are the same it gives me 1 instead of 0 so I'm subtracting one from the end – Abir Taheer Nov 23 '20 at 07:48
  • As for one day, same two days problem no need to subtract, just add instead: `if (endDate <= startDate) { return 0; }` --> `if (endDate < startDate) { return 0; } else if (endDate == startDate) { return 1; }` This will not check for holidays and weekends but who wants to add one day on it anyway? – ikiK Aug 31 '21 at 10:54
4

I took a similar approach to @OscarGarcia mainly as an excercise since my JS is rusty.

While it looks similar, it takes care not to substract a day twice if a holiday happens to be on a saturday or sunday. This way, you can pre-load a list of recurring dates (such as Dec 25th, Jan 1st, July 4th, which may or may not be on an otherwise working day -monday thru friday-)

$(document).ready(function(){
    $('#calc').click(function(){
  var d1 = $('#d1').val();
  var d2 = $('#d2').val();
        $('#dif').text(workingDaysBetweenDates(d1,d2));
    });
});
function workingDaysBetweenDates(d0, d1) {
    var startDate = parseDate(d0);
    var endDate = parseDate(d1);
    // populate the holidays array with all required dates without first taking care of what day of the week they happen
    var holidays = ['2018-12-09', '2018-12-10', '2018-12-24', '2018-12-31'];
    // Validate input
    if (endDate < startDate)
        return 0;

    var z = 0; // number of days to substract at the very end
    for (i = 0; i < holidays.length; i++)
    {
        var cand = parseDate(holidays[i]);
        var candDay = cand.getDay();

      if (cand >= startDate && cand <= endDate && candDay != 0 && candDay != 6)
      {
        // we'll only substract the date if it is between the start or end dates AND it isn't already a saturday or sunday
        z++;
      }

    }
    // Calculate days between dates
    var millisecondsPerDay = 86400 * 1000; // Day in milliseconds
    startDate.setHours(0,0,0,1);  // Start just after midnight
    endDate.setHours(23,59,59,999);  // End just before midnight
    var diff = endDate - startDate;  // Milliseconds between datetime objects    
    var days = Math.ceil(diff / millisecondsPerDay);

    // Subtract two weekend days for every week in between
    var weeks = Math.floor(days / 7);
    days = days - (weeks * 2);

    // Handle special cases
    var startDay = startDate.getDay();
    var endDay = endDate.getDay();

    // Remove weekend not previously removed.   
    if (startDay - endDay > 1)         
        days = days - 2;      

    // Remove start day if span starts on Sunday but ends before Saturday
    if (startDay == 0 && endDay != 6)
        days = days - 1  

    // Remove end day if span ends on Saturday but starts after Sunday
    if (endDay == 6 && startDay != 0)
        days = days - 1  

    // substract the holiday dates from the original calculation and return to the DOM
    return days - z;
}
function parseDate(input) {
    // Transform date from text to date
  var parts = input.match(/(\d+)/g);
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}

2018-12-09 is a sunday... with this code, it'll only be substracted once (for being a sunday) and not twice (as it would if we only checked if its a national holiday)

Javier Larroulet
  • 3,047
  • 3
  • 13
  • 30
  • Thanks for the enhancement (+1). I achieve it with `(parseDate(holidays[i]).getDay() % 6) != 0`, checking it only if it is necessary (day between selected days). – OscarGarcia Jul 12 '19 at 10:25
4

I think this solution is much more simpler

const numberOfDaysInclusive = (d0, d1) => {
  return 1 + Math.round((d1.getTime()-d0.getTime())/(24*3600*1000));
}

const numberOfWeekends = (d0, d1) => {
    const days = numberOfDaysInclusive(d0, d1); // total number of days
    const sundays = Math.floor((days + (d0.getDay() + 6) % 7) / 7); // number of sundays
    return 2*sundays + (d1.getDay()==6) - (d0.getDay()==0); // multiply sundays by 2 to get both sat and sun, +1 if d1 is saturday, -1 if d0 is sunday
}

const numberOfWeekdays = (d0, d1) => {
    return numberOfDaysInclusive(d0, d1) - numberOfWeekends(d0, d1);
}
Umair Aamir
  • 1,624
  • 16
  • 26
3

Get all weekdays between two dates:

private getCorrectWeekDays(StartDate,EndDate){
 let _weekdays = [0,1,2,3,4];
 var wdArr= [];
 var currentDate = StartDate;
 while (currentDate <= EndDate) {
  if ( _weekdays.includes(currentDate.getDay())){
    wdArr.push(currentDate);
    //if you want to format it to yyyy-mm-dd
    //wdArr.push(currentDate.toISOString().split('T')[0]);
  }
  currentDate.setDate(currentDate.getDate() +1);
}

 return wdArr;
}
MJ X
  • 8,506
  • 12
  • 74
  • 99
2

You can also try this piece of code:

const moment = require('moment-business-days');
/**
 *
 * @param {String} date - iso Date
 * @returns {Number} difference between now and @param date
 */
const calculateDaysLeft = date => {
  try {
     return moment(date).businessDiff(moment(new Date()))
  } catch (err) {
     throw new Error(err)
  }
}
Edd
  • 665
  • 1
  • 5
  • 13
  • 1
    This appears to be the best solution for me, because it allows to configure holidays for locale. – E F Feb 19 '21 at 15:30
1

The top answer actually works but with a flaw.
When the holyday is in a Saturday or Sunday it still reduces a day.

Add this to the existing code:

.... /* Here is the code */
for (var i in holidays) {
  if ((holidays[i] >= d0) && (holidays[i] <= d1)) {

    // Check if specific holyday is Saturday or Sunday
      var yourDate = new Date(holidays[i]);
      if(yourDate.getDay() === 6 || yourDate.getDay() === 0){

          // If it is.. do nothing

      } else {

          // if it is not, reduce a day..
          days--;
      }
  }
}
  • Thanks for the comment and code (+1). I achieve it with `(parseDate(holidays[i]).getDay() % 6) != 0`, checking it only if it is necessary (day between selected days). – OscarGarcia Jul 12 '19 at 10:25
  • 1
    Sure, you can do that aswell. I wrote this code that way because of the people who are not familiar with php syntax Its easy for them to understand – Bruno Vieira Jul 15 '19 at 09:25
  • It is not php syntax or code, it's javascript. [Remainder operator (`%`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder_()) and [`Date.getDay()` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay) are both javascript. – OscarGarcia Jul 15 '19 at 09:47
  • My mistake xD I was working with php when i commented it. didn't noticed i wrote php instead of javascript xD – Bruno Vieira Jul 15 '19 at 20:55
1
const workday_count = (start, end) => {
start = moment(start).format(("YYYY-MM-DD"))
end = moment(end).format(("YYYY-MM-DD"))
let workday_count = 0;
let totalDays = moment(end).diff(moment(start), "days");
let date = start
for (let i = 1; i <= totalDays; i++) {
    if (i == 1) {
        date = moment(date)
    } else {
        date = moment(date).add(1, "d");
    }
    date = new Date(date);
    let dayOfWeek = date.getDay();
    let isWeekend = (dayOfWeek === 6) || (dayOfWeek === 0);
    if (!isWeekend) {
        workday_count = workday_count + 1;
    }
}
return workday_count;

}

0

Simply reduce the length of array from the value you have got (in your fiddle)

var numberofdayswithoutHolidays= 5;
var holidays = ['2016-05-03','2016-05-05'];
alert( numberofdayswithoutHolidays - holidays.length );

You need to filter out weekends from holidays as well

holidays = holidays.filter( function(day){
  var day = parseDate( day ).getDay();
  return day > 0 && day < 6;
})
gurvinder372
  • 66,980
  • 10
  • 72
  • 94
  • It is not correct logic. For example, I will have an array with holidays: `var holidays = ['2016-05-03','2016-05-05','2016-12-31'];` And for all periods it will count -3 – AlexIL May 06 '16 at 09:50
  • @AlexIL true, if you holidays includes weekend as well. But you can run another loop to filter out weekend from this `holidays` array – gurvinder372 May 06 '16 at 09:52
  • @AlexIL check the logic to filter out holidays before substracting from the count (you already have). – gurvinder372 May 06 '16 at 10:05
0

$(document).ready(() => {
  $('#calc').click(() => {
  var d1 = $('#d1').val();
  var d2 = $('#d2').val();
    $('#dif').text(workingDaysBetweenDates(d1,d2));
  });
});

let workingDaysBetweenDates = (d0, d1) => {
  /* Two working days and an sunday (not working day) */
  var holidays = ['2016-05-03', '2016-05-05', '2016-05-07'];
  var startDate = parseDate(d0);
  var endDate = parseDate(d1);  

// Validate input
  if (endDate < startDate) {
    return 0;
  }

// Calculate days between dates
  var millisecondsPerDay = 86400 * 1000; // Day in milliseconds
  startDate.setHours(0, 0, 0, 1);  // Start just after midnight
  endDate.setHours(23, 59, 59, 999);  // End just before midnight
  var diff = endDate - startDate;  // Milliseconds between datetime objects    
  var days = Math.ceil(diff / millisecondsPerDay);

  // Subtract two weekend days for every week in between
  var weeks = Math.floor(days / 7);
  days -= weeks * 2;

  // Handle special cases
  var startDay = startDate.getDay();
  var endDay = endDate.getDay();
    
  // Remove weekend not previously removed.   
  if (startDay - endDay > 1) {
    days -= 2;
  }
  // Remove start day if span starts on Sunday but ends before Saturday
  if (startDay == 0 && endDay != 6) {
    days--;  
  }
  // Remove end day if span ends on Saturday but starts after Sunday
  if (endDay == 6 && startDay != 0) {
    days--;
  }
  /* Here is the code */
  holidays.forEach(day => {
    if ((day >= d0) && (day <= d1)) {
      /* If it is not saturday (6) or sunday (0), substract it */
      if ((parseDate(day).getDay() % 6) != 0) {
        days--;
      }
    }
  });
  return days;
}
           
function parseDate(input) {
 // Transform date from text to date
  var parts = input.match(/(\d+)/g);
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="d1" value="2016-05-02"><br>
<input type="text" id="d2" value="2016-05-08">

<p>Working days count: <span id="dif"></span></p>
<button id="calc">Calc</button>

<p>
Now it shows 5 days, but I need for example add holidays 
3 and 5 May (2016-05-03 and 2016-05-05) so the result will be 3 working days
</p>
simon
  • 11
  • 4