Search code examples
javascript

calculate holidays in 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

Solution

  • 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