ts-time-utils
Version:
A comprehensive TypeScript utility library for time, dates, durations, and calendar operations with full tree-shaking support
87 lines (86 loc) • 3.14 kB
JavaScript
/**
* Interval utilities: operations on time intervals [start, end)
*/
/** Create an interval ensuring start <= end */
export function createInterval(start, end) {
const s = new Date(start);
const e = new Date(end);
if (isNaN(s.getTime()) || isNaN(e.getTime()) || s > e)
return null;
return { start: s, end: e };
}
/** Validate an object is a proper interval */
export function isValidInterval(i) {
return !!i && i.start instanceof Date && i.end instanceof Date && !isNaN(i.start) && !isNaN(i.end) && i.start <= i.end;
}
/** Duration of interval in ms */
export function intervalDuration(i) {
return i.end.getTime() - i.start.getTime();
}
/** Whether interval contains date (inclusive start, exclusive end) */
export function intervalContains(i, date) {
const d = date instanceof Date ? date : new Date(date);
return d >= i.start && d < i.end;
}
/** Whether two intervals overlap */
export function intervalsOverlap(a, b) {
return a.start < b.end && b.start < a.end;
}
/** Intersection of two intervals, or null */
export function intervalIntersection(a, b) {
const start = a.start > b.start ? a.start : b.start;
const end = a.end < b.end ? a.end : b.end;
return start < end ? { start, end } : null;
}
/** Merge overlapping or touching intervals into a minimal set */
export function mergeIntervals(intervals) {
if (intervals.length === 0)
return [];
const sorted = [...intervals].sort((a, b) => a.start.getTime() - b.start.getTime());
const result = [];
let current = { ...sorted[0] };
for (let i = 1; i < sorted.length; i++) {
const next = sorted[i];
if (next.start <= current.end) { // overlap or touching
if (next.end > current.end)
current.end = next.end;
}
else {
result.push(current);
current = { ...next };
}
}
result.push(current);
return result;
}
/** Subtract interval b from a (can split into 0,1,2 intervals) */
export function subtractInterval(a, b) {
if (!intervalsOverlap(a, b))
return [a];
const parts = [];
if (b.start > a.start)
parts.push({ start: a.start, end: b.start });
if (b.end < a.end)
parts.push({ start: b.end, end: a.end });
return parts;
}
/** Split an interval into day-boundary intervals (UTC based) */
export function splitIntervalByDay(i) {
const res = [];
let cursor = new Date(i.start);
while (cursor < i.end) {
const dayEnd = new Date(Date.UTC(cursor.getUTCFullYear(), cursor.getUTCMonth(), cursor.getUTCDate() + 1));
const end = dayEnd < i.end ? dayEnd : i.end;
res.push({ start: new Date(cursor), end: new Date(end) });
cursor = end;
}
return res;
}
/** Total covered duration of possibly overlapping intervals */
export function totalIntervalCoverage(intervals) {
return mergeIntervals(intervals).reduce((sum, i) => sum + intervalDuration(i), 0);
}
/** Normalize array: filter invalid and merge */
export function normalizeIntervals(intervals) {
return mergeIntervals(intervals.filter(isValidInterval));
}