Reputation: 21
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.
Upvotes: 2
Views: 2256
Reputation: 21
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;
}
Upvotes: 2
Reputation: 1
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' }
]
Upvotes: 0
Reputation: 78850
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));
});
Upvotes: 0