Given a start date, end date, weekends and, holidays we will extract the valid workdays between two dates in JavaScript in this tutorial.
Sometimes we need to work backward from a date range and extract all the valid workdays within that range. Of course, we will need to exclude holidays and also days off for this period too so it is not just a simple case of subtracting the end date from the start date and adding one.
The best way to illustrate what we want to accomplish is by sharing some example data.
If you are looking to get the end work date given a start date and number of workdays, check out this tutorial:
The Example
For our example, we will need to prepare a list of date ranges, holiday periods and weekly days off (weekends).
We’ll say for convenience that we will work Monday to Friday and take Saturday and Sunday off. In JavaScirpt-land weekdays are represented as zero-based numbers with the USA-style start of the week occurring on Sunday. In this case, Sunday is zero (0) and Saturday is (6).
Let’s add in some holidays to give ourselves a well-earned break:
- 15 Sep 2024
- 31 Oct 2024 🎃🦇👻
- 24 Dec 2024
- 25 Dec 2024
- 01 Jan 2025
This could also be drawn from a regional API that provides dates for holidays in your area.
Finally, we will provide a small sample list of date ranges that we can check, but keep in mind that you can iterate over a much larger list at relatively good speed here.
The blue represents the actual workdays, while the white section indicates the actual date range. Orange bold and underlined days are holidays that don’t fall on weekends. Light red days are weekly days off (in our case, weekends).
Across the top of the dates are the days of the week as a JavaScript day running from 0 (Sunday) to 8 (Saturday).
The Video Tutorial
https://youtu.be/a2FGIDtWB_U
Releases 8 Oct 2024
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
// EXTRACT THE WORKDAYS BETWEEN TWO DATES // Given a start date, end date, weekends and holidays, extract the valid workdays between two dates. /** * Gets an array of workdays between two dates. * @param {Object} workperiod * @param {String} workperiod.startDate - Target date as valid JS date-string * @param {String} workperiod.endDate - Number of workdays. * @param {Number[]} weekends - Array of days off as JS day of week. Sunday = 0, Saturday = 6 * @param {String[]} holdays - Array of hoidays as JS-valid date-strings * @returns {Date[][]} Array of valid workdays. */ function getWorkdays(workperiod, weekends, holidays){ const dayMilliseconds = 24 * 60 * 60 * 1000 /** * Gets the UTC for current day. * @param {Sting} date * @returns {Number} UTC Date in milliseconds */ function getUTC(date){ const dt = new Date(date) return Date.UTC( dt.getFullYear(), dt.getMonth(), dt.getDate() ) } // Convert holidays of day of year as number. const holidayUTC = holidays.map(holiday => getUTC(holiday)) // Iterate over each date let workdays = workperiod.map(workday => { // -- Get day of year as number. const dayStart = getUTC(workday.startDate) const dayEnd = getUTC(workday.endDate) // Get day of week let dayOfWeek = new Date(workday.startDate).getDay() let day = dayStart let validWorkday = [] while(day <= dayEnd){ // Check if not weekends or holidays if(!weekends.includes(dayOfWeek) && !holidayUTC.includes(day)){ const dt = new Date(day).toISOString().substring(0, 10) validWorkday.push(dt) } day += dayMilliseconds dayOfWeek = dayOfWeek + 1 == 7? 0 : dayOfWeek + 1 } return validWorkday }) return workdays } /** * Main run test function */ function runsies(){ // #### SAMPLE DATA // HOLIDAYS const holidays = ["2024-09-15","2024-10-31","2024-12-24","2024-12-25","2025-01-01"] // DAYS OFF [Sun: 0, Sat: 6] <- JavaScript Day of Week Standard. const weekends = [0, 6] const testRanges = [ { startDate: '2024-09-10', endDate: '2024-09-23' }, { startDate: '2024-10-26', endDate: '2024-11-04' }, { startDate: '2024-12-22', endDate: '2025-01-16' }, ] const respArray = getWorkdays(testRanges, weekends, holidays) testRanges.map((testRange, idx) => { testRange.validDays = respArray[idx] testRange.count = respArray[idx].length return testRange }) console.log(JSON.stringify(testRanges,null, " ")) } runsies() |
The runsies() function
The runsies()
function is the main test function. It contains the 3 different variables that we will send to the getWorkdays()
function to generate our list of days.
We’re using the year-month-date (YYYY-MM-DD
) format favoured by computer nerds like me.
Once we get the list of days back from the getWordays()
function we will then map our existing testRanges
array of objects and add the valid days as a property and the total count of valid days.
Finally, we will stringify the array object to make it pretty for logging.
Running the script will display these results:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
[ { "startDate": "2024-09-10", "endDate": "2024-09-23", "validDays": [ "2024-09-10", "2024-09-11", "2024-09-12", "2024-09-13", "2024-09-16", "2024-09-17", "2024-09-18", "2024-09-19", "2024-09-20", "2024-09-23" ], "count": 10 }, { "startDate": "2024-10-26", "endDate": "2024-11-04", "validDays": [ "2024-10-28", "2024-10-29", "2024-10-30", "2024-11-01", "2024-11-04" ], "count": 5 }, { "startDate": "2024-12-22", "endDate": "2025-01-16", "validDays": [ "2024-12-23", "2024-12-26", "2024-12-27", "2024-12-30", "2024-12-31", "2025-01-02", "2025-01-03", "2025-01-06", "2025-01-07", "2025-01-08", "2025-01-09", "2025-01-10", "2025-01-13", "2025-01-14", "2025-01-15", "2025-01-16" ], "count": 16 } ] |
The getWorkdays() function
Parameters
This function takes three arguments:
workperiod
: This is the array object containing the start date and end date for each of the sample periods.weekends
: The array of designated weekends as a number. For example, Saturday and Sunday would look like this:[0,6]
holidays
: An array of holidays as date strings.
Function Variables and UTC dates
In this section, we’ll prepare our dates for efficient comparison by converting them into milliseconds so that we can extract our valid dates.
Calculating a Whole Day in Milliseconds (Line 16):
-
-
- We start by calculating the duration of a whole day in milliseconds. This will allow us to iterate over each day and check if it is a valid date.
-
Handling Timezone and Daylight Saving (Lines 22-29):
-
- To avoid any timezone-related quirks or issues with daylight-saving events, we’ll convert our date periods to Coordinated Universal Time (UTC). We’ll create a small private method at the top of our function to handle this conversion.
- Note: We’ll encounter UTC conversions multiple times in our code, so having a dedicated method simplifies things.
Converting the Array of Holiday Dates to UTC Time (Line 32):
-
- Finally, we’ll convert our holiday array of dates to UTC time in milliseconds so that we can directly reference them against the currently iterated day.
Map an array of workdays
Next, we need to map our array of found workdays for each sample date range (Line 35). To do this we will use the JavaScirpt map method that takes a function as an argument.
Map Variables
We will need to iterate through each day within our date range checking if the date is not a weekend or a holiday and then record that date. To do this, we will need to set a few variables.
Get start and end days as UTC time in milliseconds (Lines 37-38)
- Both the start date and end date will need to be converted to UTC dates using our private
getUTC()
method.
Set Mutable Variables ( Lines 41-42)
- When we look at each date we need to check that day’s day of the week. To determine the starting day of the week of our range of dates we use the JavaScirpt getDay() method. This will update for each new day we check.
- Next, we need to set our first day to check as a
day
.
Store Valid Wordays (Line 44)
- Here we store our valid workdays in the
validWorkday
array.
Iterating over the days in the range
Our main task now is to iterate over each day in the range.
Not a Weekend or a Holiday (Line 49-52)
- First, we check if the current day of the week is not included in the
weekends
array and the current day in UTC format does not exist in theholidayUTC
array. - If both of these conditions are met, then the day is a valid workday and we add it to our
validWorkay
array.
Preparing for the Next Day (Lines 54-55)
- Now we need to prepare for the next day check by:
- Updating the
day
by another day using thedayMilliseconds
variable. - Getting the next day of the week. Here, we need to keep in mind that if the next day of the week is equal to seven then it will go back to the start of the week which will be zero.
- Updating the
Returning it all back
Once we have our valid workday for the selected range, we return that array to generate our mapped array of workdays
.
Finally, we return the workdays
array back to the calling function.
If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.
That’s all there is to get a list of valid workdays in a range in JavaScript. I use this function often when providing analysis tools for clients who want to see aggregate workdays over a period of days.
I’d love to hear what you would use this function for.
~Yagi