Home > Back-end >  How to find "intersecting" time periods in js
How to find "intersecting" time periods in js

Time:05-28

Sample algorithm:

Sort all objects by the start date of the time segment. Form a new structure by looping through the sorted structure according to the algorithm: 2.1. Outside the loop, prepare a variable for a temporary object where intersecting time intervals will be accumulated. As well as an array where intersecting segments will be accumulated. Record the first time segment in it. The cycle starts from the second time period. 2.2. Check whether the current time segment “intersects” with the next one. In other words, the beginning of the next date is less than the end of the current one. 2.2.1. If it intersects, then update the end in the time structure with the end of the next segment. 2.2.2 If they do not intersect, then push into the result array. And add the following time period to the accumulation variable. The loop continues for the segment after the next (although everything will work if this is not done).

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)

How to Combine dates when they intersect?

CodePudding user response:

Use the Date object instead of parsing the string. To compare the dates, it is convenient for you to use the Date object in logic. The following example verifies that the 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;
}

Furthermore, if the date ranges intersect or are included, you can create a function that merges the dates.

/**
 * @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;
}

After that, print out your format based on the merged date.

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}`);
}

CodePudding user response:

I would suggest creating a function parseDateRanges() to parse and map the inputs to an array of { from, to } objects where from and to are Dates.

Once we have this, we'll create a mergeDateRanges() function to merge overlapping date ranges. This function will sort and merge overlapping ranges based on the required logic.

Finally we'll apply a formatter formatDateRange() to each range and join to get our output.

I've added the mentioned inputs, along with the required outputs as test cases, each should output the correct value.

// Define our 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'
    }
];

// Convert a 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 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);
}

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

// Merge any overlapping 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;
    }, [])
}

// Format a date range to 'Mar 1-30' for example..
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
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; }

  • Related