Fork me on GitHub

Source: business-time.js

'use strict'

var moment = require('moment');


/**
 * Initializes a new instance of the BusinessTime class.
 * @constructor
 */
var BusinessTime = function() {};


/**
 * Gets the holidays for th supplied year
 * @param  {String} year The year.
 * @return {Array}
 */
BusinessTime.prototype.holidaysForYear = function(year) {
  var format = 'YYYY-MM-DD';
  var startOfSept = moment(year + '-09-01', format);
  var startOfNov  = moment(year + '-11-01', format);

  return [
    // New Years
    moment(year + '-01-01', format).toISOString(),

    // Memorial Day (last Monday in May)
    moment(year + '-05-01', format).endOf('month').startOf('week').add(1, 'days').toISOString(),

    // Independence Day
    moment(year + '-07-04', format).toISOString(),

    // Labor Day (first Monday of September)
    startOfSept.format('E') == 1 ?
      startOfSept.toISOString() :
      startOfSept.startOf('week').add(1, 'week').add(1, 'day').toISOString(),

    // Thanksgiving (fourth Thurs of November)
    startOfNov.format('E') > 4 && startOfNov.format('E') != 7 ?
      startOfNov.startOf('week').add(4, 'weeks').add(4, 'day').toISOString() :
      startOfNov.startOf('week').add(3, 'weeks').add(4, 'day').toISOString(),

    // Christmas
    moment(year + '-12-25', format).toISOString()
  ];
};


/**
 * Gets the holidays for the supplied year range. Stop year IS inclusive.
 * @param {Number} start The start year.
 * @param {Number} stop  The stop year.
 */
BusinessTime.prototype.holidaysForYearRange = function(start, stop) {
  start = start * 1;
  stop  = stop * 1;

  var forward = start < stop;
  var days    = [];
  var flag    = true;

  while(flag) {
    days = days.concat(this.holidaysForYear(start));
    start = forward ? start + 1 : start - 1;
    if(forward) {
      flag = start <= stop;
    }
    else {
      flag = start >= stop;
    }
  }

  return days;
};


/**
 * Gets the time difference in bussiness days.
 * @param  {Moment} start The moment.js start date object.
 * @param  {Moment} stop  The moment.js stop date object.
 * @return {Number}
 */
BusinessTime.prototype.businessDiff = function(start, stop) {
  start = moment(start);
  stop  = moment(stop);

  var date     = moment(start);
  var holidays = this.holidaysForYearRange(start.format('YYYY'), stop.format('YYYY'));
  var forward  = stop.isAfter(start);
  var counter  = 0;

  while(date.format('DDD') !== stop.format('DDD')) {
    if(forward) {
      date.add(1, 'day');
    }
    else {
      date.subtract(1, 'day');
    }

    if(date.format('E') < 6 && !~holidays.indexOf(date.toISOString())) {
      counter++;
    }
  }

  return counter;
};


/**
 * Gets the business day from the provided date.
 * @param  {Number}  days    The number of days.
 * @param  {Moment}  date    The starting Moment object. Now by default.
 * @param  {Boolean} forward Whether the day should count up or down. False by default.
 * @return {Moment}
 */
BusinessTime.prototype.businessDayFromDate = function(days, date, forward) {
  date = (typeof date === 'undefined') ? moment() : moment(date);
  forward = (typeof forward === 'undefined') ? false : forward;
  var holidays = this.holidaysForYear(date.format('YYYY'));

  while(days !== 0) {
    if(forward) {
      date.add(1, 'day');
    }
    else {
      date.subtract(1, 'day');
    }

    if(date.format('E') < 6 && !~holidays.indexOf(date.toISOString())) {
      days--;
    }
  }

  return moment(date);
};


/**
 * Adds a specified amount of time to that supplied date.
 * @param  {Number} count The number of units to add.
 * @param  {String} unit  The time unit string.
 * @param  {Moment} date  The starting Moment object. Now by default.
 * @return {Moment}
 */
BusinessTime.prototype.add = function(count, unit, date) {
  date = (typeof date === 'undefined') ? moment() : moment(date);
  var holidays = this.holidaysForYear(date.format('YYYY'));

  date.add(count, unit);

  while(date.format('E') > 5 || ~holidays.indexOf(date.toISOString())) {
    date.add(1, 'day');
  }

  return moment(date);
};


/**
 * Subtracts a specified amount of time to that supplied date.
 * @param  {Number} count The number of units to subtract.
 * @param  {String} unit  The time unit string.
 * @param  {Moment} date  The starting Moment object. Now by default.
 * @return {Moment}
 */
BusinessTime.prototype.subtract = function(count, unit, date) {
  date = (typeof date === 'undefined') ? moment() : moment(date);
  var holidays = this.holidaysForYear(date.format('YYYY'));

  date.subtract(count, unit);

  while(date.format('E') > 5 || ~holidays.indexOf(date.toISOString())) {
    date.subtract(1, 'day');
  }

  return moment(date);
};


module.exports = new BusinessTime();