开发者

add/subtract business days in Javascript

I need a Date.prototype.addBusDays function that'll take an integer as t开发者_JS百科he number of working days to add to the date.

However, there are two considerations: 1. Weekends, 2. Holidays (which I imagine would be a preset array to compare against. If beginning date and end date contain 3 holidays, then you push out the end date by 3)

I have come across some scripts online, one dilemma I can think of is, lets say you address all the weekends first, then you do the holidays, what if you +1 day (due to holiday), and your end date is pushed into a weekends again...<

Any ideas? Thanks!

EDIT:

This is a part of a scheduling tool I am developing, which mean the dates will be tied to tasks which are linked together. Adding 1 day to a task, will trigger a recalculation of everything tied to it, potentially all dates in the database.


Datageek's solution helped me but I needed to augment it. This still doesn't do holidays but does do working days with the option of including Sat and/or Sun, and does support adding negative days:-

function AddWorkingDays(datStartDate, lngNumberOfWorkingDays, blnIncSat, blnIncSun) {
    var intWorkingDays = 5;
    var intNonWorkingDays = 2;
    var intStartDay = datStartDate.getDay(); // 0=Sunday ... 6=Saturday
    var intOffset;
    var intModifier = 0;

    if (blnIncSat) { intWorkingDays++; intNonWorkingDays--; }
    if (blnIncSun) { intWorkingDays++; intNonWorkingDays--; }
    var newDate = new Date(datStartDate)
    if (lngNumberOfWorkingDays >= 0) {
        // Moving Forward
        if (!blnIncSat && blnIncSun) {
            intOffset = intStartDay;
        } else {
            intOffset = intStartDay - 1;
        }
        // Special start Saturday rule for 5 day week
        if (intStartDay == 6 && !blnIncSat && !blnIncSun) {
            intOffset -= 6;
            intModifier = 1;
        }
    } else {
        // Moving Backward
        if (blnIncSat && !blnIncSun) {
            intOffset = intStartDay - 6;
        } else {
            intOffset = intStartDay - 5;
        }
        // Special start Sunday rule for 5 day week
        if (intStartDay == 0 && !blnIncSat && !blnIncSun) {
            intOffset++;
            intModifier = 1;
        }
    }
    // ~~ is used to achieve integer division for both positive and negative numbers
    newDate.setTime(datStartDate.getTime() + (new Number((~~((lngNumberOfWorkingDays + intOffset) / intWorkingDays) * intNonWorkingDays) + lngNumberOfWorkingDays + intModifier)*86400000));
    return newDate;
}


Have a look at the following implementation. Sourced from about.com

addWeekdays = function(date, dd) {
  var wks = Math.floor(dd/5);
  var dys = dd.mod(5);
  var dy = this.getDay();
  if (dy === 6 && dys > -1) {
     if (dys === 0) {dys-=2; dy+=2;}
     dys++; dy -= 6;
  }
  if (dy === 0 && dys < 1) {
    if (dys === 0) {dys+=2; dy-=2;}
    dys--; dy += 6;
  }
  if (dy + dys > 5) dys += 2;
  if (dy + dys < 1) dys -= 2;
  date.setDate(date.getDate()+wks*7+dys);
}

var date = new Date();
addWeekdays(date, 9);


(Updated) I've put this algorithm through its paces and it seems stable, though it does use recursion for holiday processing:

holidays = [new Date("2/13/2019"), new Date("2/19/2019")];

function addWorkdays(workdays, startDate) {
  //Make adjustments if the start date is on a weekend
  let dayOfWeek = startDate.getDay();
  let adjustedWorkdays = Math.abs(workdays);
  if (0 == dayOfWeek || 6 == dayOfWeek) {
    adjustedWorkdays += (Math.abs((dayOfWeek % 5) + Math.sign(workdays)) % 2) + 1;
    dayOfWeek = (dayOfWeek - 6) * -1;
  }
  let endDate = new Date(startDate);
  endDate.setDate(endDate.getDate() + (((Math.floor(((workdays >= 0 ? dayOfWeek - 1 : 6 - dayOfWeek) + adjustedWorkdays) / 5) * 2) + adjustedWorkdays) * (workdays < 0 ? -1 : 1)));
  //If we cross holidays, recompute our end date accordingly
  let numHolidays = holidays.reduce(function(total, holiday) { return (holiday >= Math.min(startDate, endDate) && holiday <= Math.max(startDate, endDate)) ? total + 1 : total; }, 0);
  if (numHolidays > 0) {
    endDate.setDate(endDate.getDate() + Math.sign(workdays));
    return addWorkdays((numHolidays - 1) * Math.sign(workdays), endDate);
  } else return endDate;
}


I expanded on khellendros74's answer for a project of mine that needed to disable Sundays and mailing holidays in the datepicker and return two dates on press of a button: three business days (i.e. non-holiday and non-Sunday) after the date picked in the datepicker (a field with an id of "calendar") and six business days after the date picked in the datepicker and then put those two results into a couple of disabled input fields (handDelivered and mailed). The button press calls the function calculateDates. Here is that code:

var disabledDates = ['11/11/2015', '11/26/2015', '12/25/2015', '01/01/2016','01/18/2016', '02/15/2016','05/30/2016', '07/04/2016','09/05/2016','10/10/2016','11/11/2016','11/24/2016', '12/26/2016','01/02/2017','01/16/2017', '02/20/2017','05/29/2017', '07/04/2017','09/04/2017','10/09/2017','11/10/2017','11/23/2017', '12/25/2017','01/01/2018','01/15/2018', '02/19/2018','05/28/2018', '07/04/2018','09/03/2018','10/08/2018','11/12/2018','11/22/2018', '12/25/2018','01/01/2019','01/21/2019', '02/18/2019','05/27/2019', '07/04/2019','09/02/2019','10/14/2019','11/11/2019','11/28/2019', '12/25/2019','01/01/2020','01/20/2020', '02/17/2020','05/25/2020', '07/03/2020','09/07/2020','10/11/2020','11/26/2020','11/26/2020', '12/25/2020'];

$(function(){

    $('#calendar').datepicker({
        dateFormat: 'mm/dd/yy',
        beforeShowDay: editDays
    });

    function editDays(date) {
        for (var i = 0; i < disabledDates.length; i++) {
            if (new Date(disabledDates[i]).toString() == date.toString() || date.getDay() == 0) {             
                 return [false];
            }
        }
        return [true];
     }   

});

function calculateDates()
{
    if( !$('#calendar').val()){
        alert("Please enter a date.");
        document.getElementById('calendar').focus();
        return false;
    }

    var dayThreeAdd = 0;
    var daySixAdd = 0;

    for (var i = 0; i < disabledDates.length; i++) {
        var oneDays = AddWorkingDays($('#calendar').val(),1,true,false);
        var twoDays = AddWorkingDays($('#calendar').val(),2,true,false);
        var threeDays = AddWorkingDays($('#calendar').val(),3,true,false);
        var fourDays = AddWorkingDays($('#calendar').val(),4,true,false);
        var fiveDays = AddWorkingDays($('#calendar').val(),5,true,false);
        var sixDays = AddWorkingDays($('#calendar').val(),6,true,false);

        if (new Date(disabledDates[i]).toString() == oneDays.toString()) {
             dayThreeAdd++;
             daySixAdd++;
        }
        if (new Date(disabledDates[i]).toString() == twoDays.toString()) {             
             dayThreeAdd++;
             daySixAdd++;
        }
        if (new Date(disabledDates[i]).toString() == threeDays.toString()) {             
             dayThreeAdd++;
             daySixAdd++;
        }
        if (new Date(disabledDates[i]).toString() == fourDays.toString()) {
            daySixAdd++;
        }
        if (new Date(disabledDates[i]).toString() == fiveDays.toString()) {
            daySixAdd++;
        }
        if (new Date(disabledDates[i]).toString() == sixDays.toString()) {
            daySixAdd++;
        }

    }

    var threeDays = AddWorkingDays($('#calendar').val(),(3 + dayThreeAdd),true,false);
    var sixDays = AddWorkingDays($('#calendar').val(),(6 + daySixAdd),true,false);

    $('#handDelivered').val((threeDays.getMonth()+1) + '/' + threeDays.getDate() + '/' + (threeDays.getYear()+1900));
    $('#mailed').val((sixDays.getMonth()+1) + '/' + sixDays.getDate() + '/' + (sixDays.getYear()+1900));



}

function AddWorkingDays(datStartDate, lngNumberOfWorkingDays, blnIncSat, blnIncSun) {
    datStartDate = new Date(datStartDate);
    var intWorkingDays = 5;
    var intNonWorkingDays = 2;
    var intStartDay = datStartDate.getDay(); // 0=Sunday ... 6=Saturday
    var intOffset;
    var intModifier = 0;

    if (blnIncSat) { intWorkingDays++; intNonWorkingDays--; }
    if (blnIncSun) { intWorkingDays++; intNonWorkingDays--; }
    var newDate = new Date(datStartDate)
    if (lngNumberOfWorkingDays >= 0) {
        // Moving Forward
        if (!blnIncSat && blnIncSun) {
            intOffset = intStartDay;
        } else {
            intOffset = intStartDay - 1;
        }
        // Special start Saturday rule for 5 day week
        if (intStartDay == 6 && !blnIncSat && !blnIncSun) {
            intOffset -= 6;
            intModifier = 1;
        }
    } else {
        // Moving Backward
        if (blnIncSat && !blnIncSun) {
            intOffset = intStartDay - 6;
        } else {
            intOffset = intStartDay - 5;
        }
        // Special start Sunday rule for 5 day week
        if (intStartDay == 0 && !blnIncSat && !blnIncSun) {
            intOffset++;
            intModifier = 1;
        }
    }
    // ~~ is used to achieve integer division for both positive and negative numbers
    newDate.setTime(datStartDate.getTime() + (new Number((~~((lngNumberOfWorkingDays + intOffset) / intWorkingDays) * intNonWorkingDays) + lngNumberOfWorkingDays + intModifier)*86400000));
    return newDate;
}


Simple solution to solve the whole problem; you can just loop through the days to skip weekdays and holidays:

Date.prototype.holidays = {
  // fill in common holidays
  all: [
    '0101', // Jan 01
    '1225' // Dec 25
  ],
  2016: [
    // add year specific holidays
    '0104' // Jan 04 2016
  ],
  2017: [
    // And so on for other years.
  ]
};

Date.prototype.addWorkingDays = function(days) {
  while (days > 0) {
    this.setDate(this.getDate() + 1);
    if (!this.isHoliday()) days--;
  }

  return this;
};

Date.prototype.substractWorkingDays = function(days) {
  while (days > 0) {
    this.setDate(this.getDate() - 1);
    if (!this.isHoliday()) days--;
  }

  return this;
};

Date.prototype.isHoliday = function() {
  function zeroPad(n) {
    n |= 0;
    return (n < 10 ? '0' : '') + n;
  }

  // if weekend return true from here it self;
  if (this.getDay() == 0 || this.getDay() == 6) {
    return true;
  }

  var day = zeroPad(this.getMonth() + 1) + zeroPad(this.getDate());

  // if date is present in the holiday list return true;
  return !!~this.holidays.all.indexOf(day) ||      
    (this.holidays[this.getFullYear()] ?
!!~this.holidays[this.getFullYear()].indexOf(day) : false);
};

// Uasage
var date = new Date('2015-12-31');

date.addWorkingDays(10);
alert(date.toDateString()); // Mon Jan 18 2016

date.substractWorkingDays(10);
alert(date.toDateString()) // Thu Dec 31 2015


This only takes weekends into account and not holidays, but it's a start...

function mod(x, y) {
  // https://stackoverflow.com/a/4467559/2173455
  return ((x % y) + y) % y;
}

function calculateDateDiff(date, diff) {
        let returnDate = new Date(date.getTime());
        let daysLeftToAdd = Math.abs(diff);     
        let weekendDays = 0;
        let weekDay = returnDate.getDay();
        while(daysLeftToAdd >= 0) {
          if(weekDay == 0 || weekDay == 6) {
            weekendDays++;
          }
          else {
            daysLeftToAdd--;
          }
          weekDay = mod(diff > 0 ? weekDay + 1 : weekDay - 1, 7);
        }
        returnDate.setDate(diff > 0 ?
            returnDate.getDate() + diff + weekendDays :
          returnDate.getDate() + diff - weekendDays
          );
        return returnDate;
}

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜