Tips for discovering "overlapping" time intervals in JavaScript

Here is a hypothetical algorithm for sorting and combining intersecting time segments:

First, sort all objects based on the start date of the time segment. Create a new structure by iterating through the sorted structure as follows: 2.1. Before the loop, set up a variable to temporarily store intersecting time intervals. Also, initialize an array to accumulate intersecting segments, starting with the first time segment. The loop begins from the second time period. 2.2. Check if the current time segment intersects with the next one. In other words, if the start of the next date is before the end of the current one. 2.2.1. If there is an intersection, update the end in the time structure with the end of the next segment. 2.2.2. If there is no intersection, push the result into the array. Add the subsequent time period to the accumulation variable. The loop continues for the segment after the next (although this step may be skipped).

const data = [ { "from":"01/01/2020", "to":"01/06/2020" }, { "from":"01/10/2020", "to":"01/10/2020" } ]

const monthToString = month => {
  const months = {
    '01': 'Jan',
    '02': 'Feb',
    '03': 'Mar',
    '04': 'Apr',
    '05': 'May',
    '06': 'Jun',
    '07': 'Jul',
    '08': 'Aug',
    '09': 'Sep',
    '10': 'Oct',
    '11': 'Nov',
    '12': 'Dec',
    
  }
  return months[month] || month
}

const result = Object.entries(data.reduce((res, {from, to}) => {
  const [monthFrom, day, year] = from.split('/')
  const [m, dayTo, y] = to.split('/')
  
  const month = monthToString(m)
  
  return {
   ...res,
   [month]: [...(res[month] || []), [day, dayTo]]
  }
}, {})).map(([month, days]) => `${month} ${days.map(([from, to]) => `${parseInt(from)}-${parseInt(to)}`).join(', ')}`).join('\n')

console.log(result)

Is there a way to combine dates when they intersect?

Answer №1

Instead of parsing the string, it is recommended to utilize the Date object for date comparison. The below example demonstrates how to check if two date ranges intersect.

/**
 * @param {Object} a
 * @param {string} a.from
 * @param {string} a.to
 * @param {Object} b
 * @param {string} b.from
 * @param {string} b.to
 * @returns {bool}
 */

function isDateIntersect(a, b) {
  const aFrom = new Date(a.from);
  const aTo = new Date(a.to);
  const bFrom = new Date(b.from);
  const bTo = new Date(b.to);
  return aFrom <= bFrom && aTo > bFrom || bFrom <= aFrom && bTo > aFrom;
}

If the date ranges intersect or are contained within each other, you can implement a function to combine them.

/**
 * @param {Object} a
 * @param {string} a.from
 * @param {string} a.to
 * @param {Object} b
 * @param {string} b.from
 * @param {string} b.to
 * @returns {bool|null}
 */
function mergeIntersectDate(a, b) {
  const aFrom = new Date(a.from);
  const aTo = new Date(a.to);
  const bFrom = new Date(b.from);
  const bTo = new Date(b.to);

  if (aFrom <= bFrom && aTo > bFrom || bFrom <= aFrom && bTo > aFrom) {
    return [
      new Date(Math.min(aFrom, bFrom)),
      new Date(Math.max(aTo, bTo)),
    ];
  }
  return null;
}

You can then output the formatted date range based on the merged dates.

const dateRange = mergeIntersectDate(
  {
    "from":"03/15/2021",
    "to":"03/18/2021"
  },
  {
    "from":"03/16/2021",
    "to":"03/28/2021"
  }
);
if (dateRange) {
  const [from, to] = dateRange;
  const fromMonth = months[(from.getMonth() + 1 + '').padStart(2, '0')];
  const fromDay = from.getDay();
  const toMonth = months[(to.getMonth() + 1 + '').padStart(2, '0')];
  const toDay = to.getDay();
  console.log(`${fromMonth} ${fromDay} - ${toMonth} ${toDay}`);
}

Answer №2

To efficiently handle the input data, my recommendation is to develop a function called parseDateRanges(). This function will be responsible for parsing and mapping the inputs into an array of { from, to } objects where 'from' and 'to' represent Dates.

Once this initial step is completed, we can proceed to implement a new function named mergeDateRanges(). The primary goal of this function is to merge overlapping date ranges by sorting and consolidating them based on the defined logic.

Finally, another function formatDateRange() will be utilized to format each range appropriately and then join all ranges to generate the output.

I have included the specified inputs as well as the expected outputs in the form of test cases. Each test case should produce the correct value upon execution.

// Here are our defined tests...
const testCases = [
    {
      input: [ { "from":"03/01/2021", "to":"03/06/2021" }, { "from":"03/10/2021", "to":"03/15/2021" }, { "from":"03/20/2021", "to":"03/25/2021" } ], 
      expectedOutput: 'Mar 1-6, 10-15, 20-25', 
    },
    { 
      input: [ { "from":"03/01/2021", "to":"03/05/2021" }, { "from":"03/08/2021", "to":"03/10/2021" }, { "from":"03/07/2021", "to":"03/20/2021" } ],
      expectedOutput: 'Mar 1-5, 7-20'
    },
    {
      input: [ { "from":"03/01/2021", "to":"03/05/2021" }, { "from":"03/04/2021", "to":"03/10/2021" }, { "from":"03/07/2021", "to":"03/20/2021" }, { "from":"03/20/2021", "to":"03/30/2021" } ],
      expectedOutput: 'Mar 1-30'
    },
    {
      input: [ { "from":"03/01/2021", "to":"03/05/2021" }, { "from":"03/06/2021", "to":"03/08/2021" }, { "from":"03/15/2021", "to":"03/18/2021" }, { "from":"03/16/2021", "to":"03/28/2021" } ],
      expectedOutput: 'Mar 1-8, 15-28'
    }
];

// Function to convert monthIndex (0 - 11) to string ('Jan', 'Feb' etc.)
function monthToString(monthIndex) {
    const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    return months[monthIndex];
}

// Parse a given date in MM/dd/yyyy format to a Date object.
function parseDate(str) {
    const [ month, day, year ] = str.split('/').map(Number);
    return new Date(year, month - 1, day);
}

// Function to parse input ranges into 'from' and 'to' Dates
function parseDateRanges(input) {
    return input.map(({ from, to }) => ({ from: parseDate(from), to: parseDate(to) } ));
}

// Merge any overlaps among ranges
function mergeDateRanges(ranges) {
    let sortedRanges = [ ...ranges].sort((a, b) => a.from - b.from);
    return sortedRanges.reduce((acc, range) => {
        let prevRange = acc[acc.length - 1];
        if (prevRange && (range.from <= (prevRange.to.getTime() + 86400000))) {
            prevRange.to = (range.to > prevRange.to) ? range.to : prevRange.to;
        } else {
            acc.push(range);
        }
        return acc;
    }, [])
}

// Define formatting for a date range like 'Mar 1-30' for instance..
function formatDateRange(range, idx) {
    const fromMonth = (idx === 0) ? (monthToString(range.from.getMonth()) + ' '): '';
    const toMonth = (range.from.getMonth() === range.to.getMonth()) ? '': (monthToString(range.to.getMonth()) + ' ');
    return fromMonth + range.from.getDate() + "-" + toMonth + range.to.getDate();
}

// Format a group of ranges collectively
function formatRanges(ranges) {
    let parsedRanges = parseDateRanges(ranges);
    return mergeDateRanges(parsedRanges).map(formatDateRange).join(', ')
}

function outputArr(a) {
     console.log(...a.map(s => s.padEnd(25, ' ')));
}

outputArr(['Expected', 'Actual', 'Test passed'])
for(let testCase of testCases) {
    let output = formatRanges(testCase.input);
    outputArr([testCase.expectedOutput, output, (output === testCase.expectedOutput) ? 'Yes': 'No'])
}
.as-console-wrapper { max-height: 100% !important; }

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

Guide to creating a synchronous wrapper for jQuery ajax methods

I've made the decision to switch from synchronous ajax calls to asynchronous ones due to lack of support in most modern browsers. My code is currently reliant on synchronous (and dynamic) ajax calls to client-side functions that must be completed befo ...

Having trouble retrieving parameter names when querying an Oracle database from Node.js

My current task involves querying oracledb from nodejs. Below is the code snippet I am using for performing the query: exports.simpleExecute = function(query,bindParams, options,callback) { try { pool.getConnection(function(err, connection) { if (err) { ...

Basic Timer with Visual Background

Struggling to find the right CSS/script for a straightforward countdown timer, Here are the requirements: Countdown in days only, no need for hours, minutes, and seconds Ability to use a custom image as background I've scoured online searches but n ...

Using arrays as class data members in JavaScript

As a newcomer to object-oriented JavaScript, I am attempting to create a class that has an array as a data member. This array will store objects of another class as its elements. To illustrate, consider the following example function ClassA(id, objB_01) ...

Leveraging Gulp for generating angular $templateCache on a per module/directory basis

Transitioning from grunt to gulp has been a bit challenging for me, especially when it comes to replicating the $templateCache functionality. In my Angular app, I have multiple components/modules with all their necessary resources encapsulated within each ...

What are the steps for importing and utilizing data from a JSON object?

After successfully importing data from a JSON file that was an Array, I've encountered a new challenge - my variable is now an object. My goal is to push all the questions from the object into an array and then display them on the HTML document. You c ...

Setting up Express routes in a separate file from the main one: a step-by-step

My goal is to organize my routes separately from the main app.js file using the following file structure. I attempted to create a api/user/ post API but encountered a 404 error. Any suggestions on how to resolve this issue with the given file structure? . ...

Save the libxmljs XML data into a file

Currently, I have a libxmljs XML object that I need to save to a file. Here is what I have so far: const libxml = require('libxmljs'); const xmlString = '<?xml version="1.0" encoding="UTF-8"?>' + '<root>&apo ...

Issues with Javascript functionality in Internet Explorer and Google Chrome

Hello everyone, I'm experiencing an issue with a thumb image link swapping out on hover. Oddly enough, it seems to work perfectly in Firefox and Safari, but it's not showing up on IE and Chrome. As I am relatively new to Javascript, I may be over ...

The JSON data rendered by Angular is causing disturbance

I am trying to create JSON from a multi-array in the following format: var qus ={ { "qus" :"what is your name?", "option1" : {"ans" : Alex, "cor:"false"}, "option2" : {"ans" : Hervy, "cor:"false"}, "option3" : {"ans" : Rico, "cor:"true"}, "option4" : {" ...

Issue arising when attempting to dynamically load an image using Vue and Webpack

I recently set up an application using the Vue CLI, and within one of my components, I have included the following code snippet: <template> <div v-loading="loading"> <el-carousel :interval="4000" type="card" height="350px"> & ...

sending a JSON array output to every individual <li> tag

I am working with JSON data and need to display each item inside individual <li> elements. The JSON data is structured like this: var data = [ { "MachineID":"171914", "Cost":"13,642.41", "Currency":"PHP" }, { "MachineID":"172233", ...

Collapsed ReactJS: Techniques for compressing the Material UI TreeView following expansion

My issue involves a Material UI treeView. After expanding it, an API call is made, but the node in the treeView remains open. I have attempted to use onNodeToggle without success. Below is my code: <TreeView className={classes.root1} defaultExpandI ...

What steps should be taken to retrieve a promise returned by a function?

I am a beginner when it comes to working with promises in JavaScript, so any help is greatly appreciated. I have written some code that tries to fetch data from the Firebase database and pass a list of courses from main_page.js to index.js through a functi ...

Discovering and revising an item, delivering the complete object, in a recursive manner

After delving into recursion, I find myself at a crossroads where I struggle to return the entire object after making an update. Imagine you have an object with nested arrays containing keys specifying where you want to perform a value update... const tes ...

A guide to sharing session variables with express views

Struggling to access session variables in EJS views and encountering various challenges. To locally access req.session, I've implemented middleware as outlined in this guide on accessing Express.js req or session from Jade template. var express = re ...

Having difficulty loading a JSON file containing a large array of objects for the purpose of generating a dataframe in Python

I'm currently attempting to load a json file containing approximately 2,000 objects within an array: [ { "id": 5375, "name": "cepharanthine", "mrdef": "The mechanism of action of cepharanthine ...

Learn how to efficiently process a data queue with AngularJS using Promises

Within my AngularJS application, I have a Service and multiple controllers running simultaneously. The app receives data updates from the server in JSON format through the Angular Service. To manage the vast amount of data, I need to queue it within the se ...

Retrieve and send a JSON document from Express to Jade template

Recently, I have started working with Express and Jade, but I'm stuck on why Jade is indicating that the object is undefined. I have a large JSON file that contains data about a collectible card game, structured like this: { "LEA" : { /* set data */ ...

The JSON org.json.JSONException arises when a value of type java.lang.String cannot be converted to a JSONObject

I've recently noticed this error that a lot of people have been able to solve, but as a beginner myself, I'm not sure where I've gone wrong. I wrote this code a year ago and now I really need it :/ Thank you in advance for taking the time t ...