Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

calculate holidays in Javascript

Tags:

javascript

I created the following JavaScript to calculate holidays that are not fixed. It works it seems. My questions, though, are can it be written more efficiently and did I omit anything that would improve it?

var year = new Date().getFullYear();

//  lowercase is used
var first = 1,second = 2,third = 3,fourth = 4;  // Which occurrence in a given month.  If there ma be a fifth occurrence, use "last."
var last = 99;  //  Find last occurrence of weekday in a month.  "last" set to "99".

//  lowercase used for weekday names
var sun = 0,mon = 1,tue = 2,wed = 3,thu = 4,fri = 5,sat = 6;  // JavaScript nubers weekdays 0 - 6 for Sundayear through Saturdayear.
var sunday = 0,monday = 1,tuesday = 2,wednesday = 3,thursday = 4,friday = 5,saturday = 6;

//  lowercase used for month names
var jan = 0,feb = 1,mar =2 ,apr = 3 ,may = 4,jun = 5,jul = 6,aug = 7,sep = 8,oct = 9,nov=10,dec = 11;  //  JavaScript numbers months 0 - 11, not 1 - 12.
var january = 0,february = 1,march = 2,april = 3,may = 4,june = 5,july = 6,august = 7,september = 8,october = 9,november = 10,december = 11;


function findHoliday(occurrence,weekday,month,year) {  // Find the 'first', 'second', 'third', 'fourth', or 'last' weekday occurrence in a given month and year.

  /*  Note: Numbers may be used instead of text.

      occurrence = first; second; third; fourth; or last
      weekday = sun; mon; tue; wed; thu; fri; or sat  
      month = jan; feb; mar; apr; mayear; jun; jul; aug; sep; oct; nov; or dec
      year = year from the variable 'year', or a specific year may be used such as 1990, 2010, 2017, etc.

      Syntax Examples:  findHoliday(third,mon,jan,year)     Martin Luther King, Jr. Dayear is US.
                        findHoliday(last,mon,mayear,2017)   Memorial Day in US.
  */


  /*  The most efficient method to find the 'last' (or 5th, if it exists) occurrence of a Sun, Mon, Tue, Wed, Thu, Fri, or Sat in a month is to find its first
      occurrence in the following month and then subtract 7 days from that date.  That is what the following 'if' statement does.
  */


  if (occurrence === 99) {
      var theDate = new Date(year,month+1,1+((first - (weekday >= new Date(year,month+1,1).getDay())) * 7) + (weekday - new Date(year,month+1,1).getDay())-7);
  }

  //  Finds the 'first', 'second', 'third', or 'fourth' occurrence of a weekday in a month.
  if (occurrence !== 99) {
      var theDate = new Date(year,month,1+((occurrence - (weekday >= new Date(year,month,1).getDay())) * 7) + (weekday - new Date(year,month,1).getDay()));
  }

      /* EDIT below to end of function to adapt to your needs */

    var holiday = "";

  if (occurrence == 3 && weekday == 1 && month == 0) { holiday = "Martin Luther King, Jr. Dayear"; }
  if (occurrence == 2 && weekday == 1 && month == 1) { holiday = "President's Day"; }
  if (occurrence == 2 && weekday == 0 && month == 2) { holiday = "Daylight Savings Time Begins"; }
  if (occurrence == 4 && weekday == 3 && month == 3) { holiday = "Administrative Assistants Day"; }
  if (occurrence == 2 && weekday == 0 && month == 4) { holiday = "Mother's Day"; }
  if (occurrence == 99 && weekday == 1 && month == 4) { holiday = "Memorial Day"; }
  if (occurrence == 3 && weekday == 0 && month == 5) { holiday = "Father's Day"; }
  if (occurrence == 3 && weekday == 0 && month == 6) { holiday = "Parents Day"; }
  if (occurrence == 1 && weekday == 1 && month == 8) { holiday = "Labor Day"; }
  if (occurrence == 2 && weekday == 0 && month == 8) { holiday = "Grandparents Day"; }
  if (occurrence == 99 && weekday == 0 && month == 8) { holiday = "Gold Star Mothers Day"; }
  if (occurrence == 2 && weekday == 1 && month == 9) { holiday = "Columbus Day"; }
  if (occurrence == 1 && weekday == 0 && month == 10) { holiday = "Daylight Savings Time Ends"; }
  if (occurrence == 4 && weekday == 4 && month == 10) { holiday = "Thanksgiving Day"; }


  var weekday = new Array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday");
  var mMonth = new Array("January","February","March","April","May","June","July","August","September","October","November","December");

  var displayDate = "";

  if (holiday == ""){
     var displayDate = weekday[theDate.getDay()] + ', ' + mMonth[theDate.getMonth()] + ' ' + theDate.getDate() + ', ' + year;
  }

  if (holiday != "") {
      var displayDate = weekday[theDate.getDay()] + ', ' + mMonth[theDate.getMonth()] + ' ' + theDate.getDate() + ', ' + year + '    ' + holiday;
  }   

    return displayDate;

}  //  End of 'findHoliday(o,d,m,year)' function


 // Examples Only:  Delete as is not part of this script.
 document.write(findHoliday(third,sunday,june,year) + '<p>')
   document.write(findHoliday(3,0,5,2015));
 // End of Examples
like image 812
Steven Gregory Avatar asked Sep 02 '15 00:09

Steven Gregory


3 Answers

Instead of that block of if statements, you should have a dictionary of holidays accessible with a key. Your function should just construct a key from the input and see if a holiday exists with that key.

I would break out the functionality to find holidays and find dates into separate functions. You can then have a third function that returns a string representation of the date, including the holiday if any.

Do every thing zero based. If you want the first occurrence of a day in the month, pass 0. To count from the end of the month, pass negative numbers indicating how many weeks to count back. So, for the last occurrence of a day in the month, pass -1. These changes make it easy to find a date using math.

The built-in Date.toLocaleDateString() already produces the date string you are looking for. You might consider using that instead. Either way, don't repeat the code that constructs the date string. Create the date string, then if there is a holiday, append it to the date string.

const holidays = { // keys are formatted as month,week,day
    "0,2,1": "Martin Luther King, Jr. Day",
    "1,2,1": "President's Day",
    "2,1,0": "Daylight Savings Time Begins",
    "3,3,3": "Administrative Assistants Day",
    "4,1,0": "Mother's Day",
    "4,-1,1": "Memorial Day",
    "5,2,0": "Father's Day",
    "6,2,0": "Parents Day",
    "8,0,1": "Labor Day",
    "8,1,0": "Grandparents Day",
    "8,-1,0": "Gold Star Mothers Day",
    "9,1,1": "Columbus Day",
    "10,0,0": "Daylight Savings Time Ends",
    "10,3,4": "Thanksgiving Day"
};
function getDate(year, month, week, day) {
    const firstDay = 1;
    if (week < 0) {
        month++;
    }
    const date = new Date(year, month, (week * 7) + firstDay);
    if (day < date.getDay()) {
        day += 7;
    }
    date.setDate(date.getDate() - date.getDay() + day);
    return date;
}
function getHoliday(month, week, day) {
    return holidays[month + "," + week + "," + day];
}
function getDateString(year, month, week, day) {
    const date = getDate(year, month, week, day);
    const holiday = getHoliday(month, week, day);
    let dateString = date.toLocaleDateString();
    if (holiday) {
        dateString += " \xa0\xa0\xa0" + holiday;
    }
    return dateString;
}
console.log(getDateString(2021, 4, -1, 1)); // Memorial Day, 2021
like image 99
gilly3 Avatar answered Oct 13 '22 06:10

gilly3


I had problems with @gilly3 's answer trying to use it with the current year (2019). Most of the holiday dates generated were off by about a week. So I adapted an answer from here that works for me

Important thing to note about this version: instead of using "-1" for the last holiday of the month, it uses "5". And "week" is not longer zero-based (like gilly3's answer) but is "1-based" (like OP's original code)

var holidays = { // keys are formatted as month,week,day
    "0,3,1": "Martin Luther King, Jr. Day",      //3rd Mon in Jan
    "1,3,1": "President's Day",                  //3rd Mon in Feb
    "2,2,0": "Daylight Savings Time Begins",     //2nd Sun in Mar
    "3,3,3": "Administrative Professionals Day", //3rd Wed in Apr
    "4,2,0": "Mother's Day",                     //2nd Sun in May (in the US, but not all countries)
    "4,5,1": "Memorial Day",                     //Last Mon in May
    "5,3,0": "Father's Day",                     //3rd Sun in Jun (in the US, but not all countries)
    "6,4,0": "Parents Day",                      //4th Sun of Jul
    "8,1,1": "Labor Day",                        //1st Mon in Sept
    //"8,1,0": "Grandparents Day",               //Technically this is the 1st Sun AFTER Labor Day
                                                 //  so can't be represented perfectly this way
    "8,5,0": "Gold Star Mothers Day",            //Last Sun in Sep
    "9,2,1": "Columbus Day",                     //2nd Mon in Oct
    "10,1,0": "Daylight Savings Time Ends",      //1st Sun in Nov
    "10,4,4": "Thanksgiving Day"                 //4th Thurs in Nov
};

function getDate(year, month, week, day) {
  var date = new Date(year, month, 1),
      add = (day - date.getDay() + 7) % 7 + (week - 1) * 7;

  // make sure that we stay in the same month
  do {
    date.setMonth(month);
    date.setDate(1 + add);
    add -= 7;
  } while (date.getMonth() != month);

  return date;
}

function getHoliday(month, week, day) {
    return holidays[month + "," + week + "," + day];
}

function getDateString(year, month, week, day) {
    var date = getDate(year, month, week, day);
    var holiday = getHoliday(month, week, day);
    var dateString = date.toLocaleDateString();
    if (holiday) {
        dateString += " \xa0\xa0\xa0" + holiday;
    }
    return dateString;
}
like image 25
derekantrican Avatar answered Oct 13 '22 06:10

derekantrican


If you dont have the need to count the holidays but only care to know whether a given day is a holiday then you can use the following plain javascript function that doesnt use any libraries.

This function returns name of the holiday if input date is holiday otherwise returns empty string.

This solution satisfies following requirements that I had:

  • No Frameworks
  • Ability to define fix date holidays (e.g. 1st Jan is holiday) in human readable format (avoiding zero based scales)
  • Ability to define relative holidays (e.g. 1st Monday of September is holiday) in as human readable format as possible.
  • If possible make single javascript function with say only 50 lines of code

.

function isBankHoliday(date) {
    // static holidays
    const isDate = (d, month, date) => {
        return d.getMonth() == (month - 1) && d.getDate() == date;
    };
    if (isDate(date, 1, 1)) { return "New Year"; }
    else if (isDate(date, 7, 4)) { return "Independence Day"; }
    else if (isDate(date, 11, 11)) { return "Veterans Day"; }
    else if (isDate(date, 12, 25)) { return "Christmas Day"; }

    // dynamic holidays
    const isDay = (d, month, day, occurance) => {
        if (d.getMonth() == (month - 1) && d.getDay() == day) {
            if (occurance > 0) {
                return occurance == Math.ceil(d.getDate() / 7);
            } else {
                // check last occurance
                let _d = new Date(d);
                _d.setDate(d.getDate() + 7);
                return _d.getMonth() > d.getMonth();
            }
        }
        return false;
    };
    if (isDay(date, 1, 1, 3)) { return "MLK Day"; }
    else if (isDay(date, 2, 1, 3)) { return "Presidents Day"; }
    else if (isDay(date, 5, 1, -1)) { return "Memorial Day"; }
    else if (isDay(date, 9, 1, 1)) { return "Labor Day"; }
    else if (isDay(date, 10, 1, 2)) { return "Columbus Day"; }
    else if (isDay(date, 11, 4, 4)) { return "Thanksgiving Day"; }

    // Non Business days
    if (date.getDay() == 0) { return "Sunday"; }
    else if (date.getDay() == 6) { return "Saturday" }

    // not a holiday
    return "";
}

Tests:

function isBankHoliday(date) {
    
        // static holidays
        const isDate = (d, month, date) => {
            return d.getMonth() == (month - 1) && d.getDate() == date;
        };
        if (isDate(date, 1, 1)) { return "New Year"; }
        else if (isDate(date, 7, 4)) { return "Independence Day"; }
        else if (isDate(date, 11, 11)) { return "Veterans Day"; }
        else if (isDate(date, 12, 25)) { return "Christmas Day"; }
    
        // dynamic holidays
        const isDay = (d, month, day, occurance) => {
            if (d.getMonth() == (month - 1) && d.getDay() == day) {
                if (occurance > 0) {
                    return occurance == Math.ceil(d.getDate() / 7);
                } else {
                    // check last occurance
                    let _d = new Date(d);
                    _d.setDate(d.getDate() + 7);
                    return _d.getMonth() > d.getMonth();
                }
            }
            return false;
        };
        if (isDay(date, 1, 1, 3)) { return "MLK Day"; }
        else if (isDay(date, 2, 1, 3)) { return "Presidents Day"; }
        else if (isDay(date, 5, 1, -1)) { return "Memorial Day"; }
        else if (isDay(date, 9, 1, 1)) { return "Labor Day"; }
        else if (isDay(date, 10, 1, 2)) { return "Columbus Day"; }
        else if (isDay(date, 11, 4, 4)) { return "Thanksgiving Day"; }
    
        // Non Business days
        if (date.getDay() == 0) { return "Sunday"; }
        else if (date.getDay() == 6) { return "Saturday" }
    
        // not a holiday
        return "";
    }
    
    
    console.log(isBankHoliday(new Date(2020, 00, 01)));
    console.log(isBankHoliday(new Date(2020, 06, 04)));
    console.log(isBankHoliday(new Date(2020, 10, 11)));
    console.log(isBankHoliday(new Date(2020, 11, 25)));

    console.log(isBankHoliday(new Date(2020, 00, 20)));
    console.log(isBankHoliday(new Date(2020, 01, 17)));
    console.log(isBankHoliday(new Date(2020, 04, 25)));
    console.log(isBankHoliday(new Date(2020, 08, 07)));
    console.log(isBankHoliday(new Date(2020, 09, 12)));
    console.log(isBankHoliday(new Date(2020, 10, 26)));

    console.log(isBankHoliday(new Date(2017, 00, 16)));
    console.log(isBankHoliday(new Date(2017, 01, 20)));
    console.log(isBankHoliday(new Date(2017, 04, 29)));
    console.log(isBankHoliday(new Date(2017, 08, 04)));
    console.log(isBankHoliday(new Date(2017, 09, 09)));
    console.log(isBankHoliday(new Date(2017, 10, 23)));

    // Weekends
    console.log(isBankHoliday(new Date(2020, 0, 4)));
    console.log(isBankHoliday(new Date(2020, 0, 12)));

    // Negatives
    console.log(isBankHoliday(new Date(2020, 00, 02)));
    console.log(isBankHoliday(new Date(2020, 04, 18)));
    console.log(isBankHoliday(new Date(2020, 04, 26)));
    console.log(isBankHoliday(new Date(2017, 04, 22)));
    console.log(isBankHoliday(new Date(2017, 04, 30)));
    console.log(isBankHoliday(new Date(2017, 04, 31)));
    console.log(isBankHoliday(new Date(2020, 0, 6)));
    console.log(isBankHoliday(new Date(2020, 0, 8)));
    console.log(isBankHoliday(new Date(2020, 0, 10)));
    console.log(isBankHoliday(new Date(2020, 0, 31)));
like image 37
Parag Kutarekar Avatar answered Oct 13 '22 06:10

Parag Kutarekar