简体   繁体   中英

Merge overlapping date ranges

How can I merge overlapping date ranges, on an array?

I have an array of dates:

const dates = [
  {startDate: "2020-03-19T00:00:00+01:00", endDate: "2020-03-20T00:00:00+01:00"},
  {startDate: "2020-03-09T00:00:00+01:00", endDate: "2020-03-16T00:00:00+01:00"},
  {startDate: "2020-02-07T00:00:00+01:00", endDate: "2020-03-09T00:00:00+01:00"},
  {startDate: "2020-02-07T00:00:00+01:00", endDate: "2020-02-13T00:00:00+01:00"}
];

What I'm looking to accomplish is to have the overlapping arrays merged so I would have as result:

//Result I'm looking for:
const mergedDates = [
  {startDate: "2020-03-19T00:00:00+01:00", endDate: "2020-03-20T00:00:00+01:00"},
  {startDate: "2020-02-07T00:00:00+01:00", endDate: "2020-03-16T00:00:00+01:00"}
];

I'm using the moment-range to create the ranges:

const ranges = dates.map(d => {
  return moment.range(d.startDate, d.endDate);
});

Then I'm using two for loops to find out the overlapping

  let arrRange = [];

  for (let i = 0; i < ranges.length; i++) {
    const el1 = ranges[i];
    let loop=[];
    for (let i = 0; i < ranges.length; i++) {
      const el2 = ranges[i];
      const overlaps = el1.overlaps(el2, { adjacent: true });
      if(overlaps){
        loop = [...loop, i]
      }
    }
    arrRange.push(loop);
  }
}

This gives me an array where I have arrays of indexes, so I know where are the overlaps:

console.log(arrRange);
// [[0], [1, 2], [1, 2, 3], [2, 3]]

However, I'm stuck.

Even knowing the overlaps, I can't figure out how to merge them.

I'd use Math.min and Math.max to get the first and last date endpoints for each overlapping range, then reduce each index array to a merged range object:

const merged = arrRange.map(idxArray => {
  const { min, max } = idxArray.reduce(({ min, max }, index) => {
    const range = ranges[index];
    return {
      min: Math.min(min, range.start.toDate()),
      max: Math.max(max, range.end.toDate())
    };
  }, { min: Number.MAX_VALUE, max: Number.MIN_VALUE }); // throwaway value
  return moment.range(new Date(min), new Date(max));
});
        var rawArray = [
        { name: 'C', startDate: '2021/02/01', endDate: '2021/02/15' },
        { name: 'A', startDate: '2021/01/01', endDate: '2021/01/15' },
        { name: 'D', startDate: '2021/02/12', endDate: '2021/02/25' },
        { name: 'E', startDate: '2021/02/22', endDate: '2021/02/28' },
        { name: 'F', startDate: '2021/03/01', endDate: '2021/03/15' },
        { name: 'B', startDate: '2021/01/12', endDate: '2021/01/30' },
    ]
    console.log('Input', rawArray)
    
        for (var i = 0; i < rawArray.length; i++) {
            for (var j = i + 1; j < rawArray.length; j++) {
                if (isBetweenOrOverlap(rawArray[i].startDate, rawArray[i].endDate, rawArray[j].startDate, rawArray[j].endDate)) {
                    rawArray[i].startDate = getLeastDate(rawArray[i].startDate, rawArray[j].startDate);
                    rawArray[i].endDate = getMaxDate(rawArray[i].endDate, rawArray[j].endDate);
                    rawArray.splice(j, 1);
                    i = i - 1;
                    break;
                }
            }
        }
    
        console.log('Ouput', rawArray)

Input [
  { name: 'C', startDate: '2021/02/01', endDate: '2021/02/15' },
  { name: 'A', startDate: '2021/01/01', endDate: '2021/01/15' },
  { name: 'D', startDate: '2021/02/12', endDate: '2021/02/25' },
  { name: 'E', startDate: '2021/02/22', endDate: '2021/02/28' },
  { name: 'F', startDate: '2021/03/01', endDate: '2021/03/15' },
  { name: 'B', startDate: '2021/01/12', endDate: '2021/01/30' }
]
Ouput [
  { name: 'C', startDate: '2021/02/01', endDate: '2021/02/28' },
  { name: 'A', startDate: '2021/01/01', endDate: '2021/01/30' },
  { name: 'F', startDate: '2021/03/01', endDate: '2021/03/15' }
]

I had to do this today (typescript, but you get the idea):

interface DateRange {
  start: Date;
  end: Date;
}
export function mergeOverlappingDateRanges(
  dateRanges: DateRange[],
): DateRange[] {
  const sorted = dateRanges.sort(
    // By start, ascending
    (a, b) => a.start.getTime() - b.start.getTime(),
  );

  const ret = sorted.reduce((acc, curr) => {
    // Skip the first range
    if (acc.length === 0) {
      return [curr];
    }

    const prev = acc.pop();

    if (curr.end <= prev.end) {
      // Current range is completely inside previous
      return [...acc, prev];
    }

    // Merges overlapping (<) and contiguous (==) ranges
    if (curr.start <= prev.end) {
      // Current range overlaps previous
      return [...acc, { start: prev.start, end: curr.end }];
    }

    // Ranges do not overlap
    return [...acc, prev, curr];
  }, [] as DateRange[]);

  return ret;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM