How can one determine the number of months worked within a specific date range?

I am currently faced with the challenge of determining the number of effective working months within given date ranges. This involves calculating the ratio of working days to total days in each month, excluding weekends.

effective working month = Number of working days work per month / total working days of that month

working days are defined as days without weekends.

The current algorithm being used is:

Total working months = number of working months in first month + number of working months in last month + number of months in between

This approach has helped simplify calculations without the need for loops. However, the code is lengthy and contains multiple functions. Unfortunately, I cannot share the code due to data sensitivity. I would appreciate any insights into a more efficient algorithm to optimize performance.

//Unit test with start date = 2020-05-20, end date = 2021-08-11, expected result of 14.74458875

Number of months in first month = workingDays(2020-05-20,2020-05-31) / workingdays(2020-05-01,2020-05-31)
                             = 0.380952381 
Number of months in last month = workingDays(2021-08-01,2021-08-11) / workingdays(2021-08-01,2021-08-31) 
                              = 0.363636364
Months in between = 14
Total months = 0.380952381 + 14 + 0.363636364
            = 14.74458875

Answer №1

Here is a suggested function that handles both dates equally within a loop iteration. To simplify the counting process, a pattern string is utilized with "x" representing working days. By extracting the relevant substring from the pattern and removing non-"x" characters (weekend days), the resulting length indicates the number of working days. The calculation then involves summing up various elements:

function calculateWorkingDays(startDate, endDate) {
    let monthCounter = -1;
    for (let i = 0; i < 2; i++) {
        let date = i ? endDate : startDate;
        let year = +date.slice(0, 4);
        let month = date.slice(5, 7) - 1;
        let day = +date.slice(8);
        let weekday = new Date(year, month, 1).getDay();
        let pattern = ":xxxxx::xxxxx::xxxxx::xxxxx::xxxxx::x"
                .slice(weekday, weekday + new Date(year, month + 1, 0).getDate());
        monthCounter += (year * 12 + month) * (i ? 1 : -1) 
           + pattern.slice(i ? 0 : day - 1, i ? day : 31).replace(/:/g, "").length
             / pattern.replace(/:/g, "").length;
    }
    return monthCounter;
}

// Example using the given dates
console.log(calculateWorkingDays("2020-05-20", "2021-08-11")); // 14.7445887445...

Answer №2

Feast your eyes on an implementation that mirrors the algorithm you mentioned. Whether it enhances code readability or performance remains a mystery:

// Handy utility functions

const range = (lo, hi) =>
  [... Array (hi - lo + 1)] .map ((_, i) => lo + i)

const parseDate = (s, [y, m, d] = s.split('-') .map (Number)) =>
  [y, m -1 , d]  // JS Dates start month at index 0

const isLeapYear = (y) =>
  (y % 4 == 0) && (y % 100 != 0 || y % 400 == 0)

const daysInMonth = (y, m) =>
  isLeapYear (y)
    ? [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] [m]
    : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] [m]

// Auxiliary functions

const fullMonthsBetween = (y1, m1, y2, m2) => // excludes both endpoints
  Math.max((12 * y2 + m2) - (12 * y1 + m1) - 1, 0)

const workingDaysInRemainderOfMonth = (y, m, d, day = new Date (y, m, d) .getDay ()) =>
  range (d, daysInMonth (y, m))
    .filter ((_, i) => [1, 2, 3, 4, 5] .includes((day + i) % 7))
    .length

const workingDaysInStartOfMonth = (y, m, d) =>
  range (1, d)
    .filter ((_, i) => [1, 2, 3, 4, 5] .includes ((d + i) % 7))
    .length

// Main function

const workingMonthsBetween = (start, end) => {
  const [y1, m1, d1] = parseDate (start)
  const [y2, m2, d2] = parseDate (end)
  return fullMonthsBetween (y1, m1, y2, m2)
         + workingDaysInRemainderOfMonth (y1, m1, d1)
            / workingDaysInRemainderOfMonth(y1, m1, 1)
         + workingDaysInStartOfMonth (y2, m2, d2)
            / workingDaysInRemainderOfMonth(y2, m2, 1)
}

// Demo
console .log (
  workingMonthsBetween ('2020-05-20', '2021-08-12')  //~> 14.744588744588745
)

This solution consists of several small, modular functions. While consolidating them could boost efficiency, I find the clarity and maintainability of distinct helper functions preferable to a monolithic approach.

  • range generates a range of integers.

    range (3, 12) //=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

  • parseDate converts a date string like '2020-08-12' into year/month/day values [2010, 7, 12]. Adjusting for JavaScript's zero-indexed months accounts for the subtraction by one.

  • isLeapYear serves its self-explanatory purpose while considering leap year intricacies.

  • daysInMonth calculates the total days in a month, accounting for leap years.

  • fullMonthsBetween provides the count of whole months between two year/month pairs, excluding the start and end points.

  • workingDaysInRemainderOfMonth tallies the remaining workdays in a given month from a specific point onward by filtering out weekends using essential arithmetic.

  • workingDaysInStartOfMonth similarly computes workdays starting from the beginning of a month up to a specified date.

  • workingMonthsBetween represents the central functionality, determining the months span between two ISO-8601 date strings based on the underlying auxiliary functions.

Holiday considerations are currently omitted. Incorporating them would elevate complexity beyond mere triviality.

Additional Note

A brief exploration was undertaken regarding Zeller's Congruence, which reveals superior speed compared to utilizing the Date constructor isolatedly. Referencing performance benchmarks supports this observation:

// General-purpose utility functions
const range = (lo, hi) => 
  [... Array (hi - lo + 1)] .map ((_, i) => lo + i)

// Rest of code block retains similar structure as previous snippet for brevity
...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to invoke fancybox using a class selector?

I'm having trouble using fancybox on an element with this code: $(this).parent().parent().parent().find('.comments').fancybox(); Is it not feasible? Could it be because it's a class and not an id? I have multiple rows of items, so as ...

Automatically shutting windows using jQuery after a certain number of seconds

I am currently trying to find a solution to this issue. The problem I am facing is that I need to close a window after it has been open for 7 seconds each time. I have managed to make the windows close using SetInterval, however, the timing is not precise ...

The JSON file is not filling the options in the dropdown menu

procedure Add the objects from the dropdown into the dropdown menu. The Json file is located in root/ajax/.json and the working file is stored in root/.html. Problem: None of the objects from the JSON file are appearing in the dropdown menu. I attempted ...

Tips for avoiding recursive error function calls in Angular 5

Is there a way to avoid repetitive function calls within a recursive function? Take a look at the following code snippet: loadFinalData(id, color){ this.data = this._test.getUrl(id, "white"); this.dataHover = this._test.getUrl(id, "blue"); } pri ...

Activate the next tab by clicking a button in ReactJS

I currently have a web page with 4 tabs, each containing different sets of data. Within the first tab, I have a button that should activate the next tab's content when clicked by the user. render(){ return ( <MuiThemeProvider> ...

Animating avatars with Three.js

Seeking some guidance on utilizing the three.js library for creating animations. Specifically, I am interested in animating an avatar that has been exported from Blender to Collada format (.dae), to move an arm or a leg. I would like to achieve this anim ...

I am requesting for the average to be adjusted based on the user's choice between Hindi or English percentage scale

Currently, the average is being calculated in the HTML code. When a user input is changed, the average breaks. What can be done to update the average for each individual person? Controller var app = angular.module('myApp', []); app.controller( ...

How to effectively utilize higher order map in RxJS?

const observable$ = combineLatest([searchAPI$, searchByID$]) // Line 1 .pipe( map(data => { let APISearchResult = data[0]; // This is an array of SearchResult type const searchByIDRawData = data[1]; // ...

Tips for successfully transferring property values while utilizing Ajax in Struts 2

After configuring Struts 2 with Ajax, I encountered an issue where the form properties were not being passed to the action class, resulting in a value of null. This is my JSP: <head> <title>Login</title> <meta charset="ut ...

How can I use JQuery to iterate through Object data and dynamically create tr and td elements?

In previous projects, I utilized Vanilla JS to iterate through Ajax return data and construct tables. However, for this current project, I have decided to switch over to JQuery. The main reason behind this decision is that some of the td elements require s ...

Utilize Element-UI to effectively close dropdown menus

In my Vue application with ElementUI, I have a drop down menu that needs to be explicitly closed after some logic is applied. The process is as follows: User loads the page, selects a name from the drop-down - the name value is saved in the database User ...

Creating an index signature in TypeScript without having to use union types for the values - a comprehensive guide

Is it possible to define an index signature with strict type constraints in TypeScript? interface Foo { [index: string]: number | string } However, I require the value type to be either number or string specifically, not a union of both types (number | ...

Strategies for choosing the perfect GIF to showcase in a React JS project

Hey everyone, I'm completely new to working with react JS and I've encountered a problem. I have a task where I need to display GIFs when a button is clicked, but I'm struggling to select a specific GIF. Can anyone provide some guidance on f ...

What is the method for extending props from React.HTMLProps<HTMLButtonElement>?

"react": "^17.0.2", "typescript": "^4.2.4" Can someone help me understand how to extend props from React.HTMLProps? import { FC, HTMLProps } from 'react' export interface SliderButtonProps extends HTMLPro ...

The issue of ERR_MODULE_NOT_FOUND in Node.js express.Router arises when attempting to import new routes

Something strange is happening. I was in the process of organizing my routes by creating a new folder. Within this folder, I used the express.Router API to define the routes and then exported the router itself. Here is an example code snippet from my pos ...

Styling a table based on specific conditions using Angular and JavaScript

I am trying to conditionally style the text in the table based on the time elapsed since the last ping. Specifically, I want to change the color of the text from green to red once 5 minutes have passed and vice versa. <form name="myform" data-ng-submit ...

Retrieve the shortened version of the month from a non-English date

Looking for a solution to convert dates from a text file into a datatable? Here's a possible approach: data$fecha <- as.Date(data$textDate , "%d-%b-%Y") One challenge you might face is that the dates are in Spanish, meaning abbreviations like Ene ...

Utilize Regular Expression Constant Validator in Angular 8's Reactive Formbuilder for efficient data validation requirements

Is there a way to efficiently store and reuse a Regex Validator pattern in Angular while following the DRY principle? I have a reactive formbuilder with a Regex validator pattern for ZipCode that I need to apply to multiple address forms. I'm interes ...

What is the recommended approach for testing a different branch of a return guard using jest?

My code testing tool, Jest, is indicating that I have only covered 50% of the branches in my return statement test. How can I go about testing the alternate branches? The code snippet below defines a function called getClient. It returns a collection h ...

Interruption of client-side JavaScript by the ASP timer tick event

Exploring the Situation In the realm of web development, I find myself immersed in creating a web application dedicated to monitoring various services. Each service is equipped with an UpdatePanel, showcasing status updates along with interactive buttons ...