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
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
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;
}
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:
.
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)));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With