Date Range Overlap And Merge
Detect overlapping date ranges, compute their intersection duration, and merge touching or overlapping ranges.
Code
export interface DateRange {
start: Date;
end: Date;
}
function normalize(range: DateRange): DateRange {
if (!(range.start instanceof Date) || !(range.end instanceof Date)) {
throw new TypeError("range bounds must be Date instances");
}
if (Number.isNaN(range.start.getTime()) || Number.isNaN(range.end.getTime())) {
throw new RangeError("range bounds must be valid dates");
}
return range.start <= range.end
? { start: range.start, end: range.end }
: { start: range.end, end: range.start };
}
export function rangesOverlap(left: DateRange, right: DateRange): boolean {
const a = normalize(left);
const b = normalize(right);
return a.start.getTime() <= b.end.getTime() && b.start.getTime() <= a.end.getTime();
}
export function overlapDurationMs(left: DateRange, right: DateRange): number {
const a = normalize(left);
const b = normalize(right);
const start = Math.max(a.start.getTime(), b.start.getTime());
const end = Math.min(a.end.getTime(), b.end.getTime());
return Math.max(0, end - start);
}
export function mergeDateRanges(ranges: DateRange[]): DateRange[] {
const sorted = ranges.map(normalize).sort((a, b) => a.start.getTime() - b.start.getTime());
const merged: DateRange[] = [];
for (const range of sorted) {
const last = merged[merged.length - 1];
if (last && range.start.getTime() <= last.end.getTime()) {
if (range.end.getTime() > last.end.getTime()) last.end = new Date(range.end);
} else {
merged.push({ start: new Date(range.start), end: new Date(range.end) });
}
}
return merged;
}Report a problem with this exampleOpens a pre-filled issue on GitHub.