UNPKG

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