UNPKG

ts-time-utils

Version:

A comprehensive TypeScript utility library for time, dates, durations, and calendar operations with full tree-shaking support

110 lines (109 loc) 4.08 kB
/** * Working hours utilities for business time calculations */ export const DEFAULT_WORKING_HOURS = { workingDays: [1, 2, 3, 4, 5], hours: { start: 9, end: 17 }, breaks: [{ start: 12, end: 13 }] }; /** Check if a date is a configured working day */ export function isWorkingDay(date, config = DEFAULT_WORKING_HOURS) { return config.workingDays.includes(date.getDay()); } /** Convert date to fractional hour */ function toHourFraction(date) { return date.getHours() + date.getMinutes() / 60 + date.getSeconds() / 3600; } /** Check if inside working hours (excluding breaks) */ export function isWorkingTime(date, config = DEFAULT_WORKING_HOURS) { if (!isWorkingDay(date, config)) return false; const h = toHourFraction(date); if (h < config.hours.start || h >= config.hours.end) return false; if (config.breaks) { for (const b of config.breaks) { if (h >= b.start && h < b.end) return false; } } return true; } /** Move date forward to next working minute */ export function nextWorkingTime(date, config = DEFAULT_WORKING_HOURS) { const d = new Date(date); while (!isWorkingTime(d, config)) { d.setMinutes(d.getMinutes() + 1); } return d; } /** Clamp a date into working window of its day */ function clampToWorkingWindow(date, config) { if (!isWorkingDay(date, config)) return null; const start = new Date(date); start.setHours(config.hours.start, 0, 0, 0); const end = new Date(date); end.setHours(config.hours.end, 0, 0, 0); if (date < start) return start; if (date > end) return null; return date; } /** Compute working time (ms) between two dates */ export function workingTimeBetween(start, end, config = DEFAULT_WORKING_HOURS) { if (end <= start) return 0; let total = 0; const cursor = new Date(start); while (cursor < end) { if (isWorkingDay(cursor, config)) { const windowStart = new Date(cursor); windowStart.setHours(config.hours.start, 0, 0, 0); const windowEnd = new Date(cursor); windowEnd.setHours(config.hours.end, 0, 0, 0); const rangeStart = cursor > windowStart ? cursor : windowStart; const rangeEnd = end < windowEnd ? end : windowEnd; if (rangeStart < rangeEnd) { let segment = (rangeEnd.getTime() - rangeStart.getTime()) / 1000 / 60 / 60; // hours // subtract breaks if (config.breaks) { for (const b of config.breaks) { const bStart = new Date(rangeStart); bStart.setHours(Math.floor(b.start), (b.start % 1) * 60, 0, 0); const bEnd = new Date(rangeStart); bEnd.setHours(Math.floor(b.end), (b.end % 1) * 60, 0, 0); const overlapStart = bStart > rangeStart ? bStart : rangeStart; const overlapEnd = bEnd < rangeEnd ? bEnd : rangeEnd; if (overlapStart < overlapEnd) { segment -= (overlapEnd.getTime() - overlapStart.getTime()) / 1000 / 60 / 60; } } } total += segment; } } // advance to next day start cursor.setHours(24, 0, 0, 0); } return total * 60 * 60 * 1000; // ms } /** Advance by working hours amount (simple iterative approach) */ export function addWorkingHours(start, hours, config = DEFAULT_WORKING_HOURS) { if (hours <= 0) return new Date(start); let remaining = hours * 60; // minutes let cursor = new Date(start); cursor = nextWorkingTime(cursor, config); while (remaining > 0) { if (isWorkingTime(cursor, config)) { cursor.setMinutes(cursor.getMinutes() + 1); remaining -= 1; } else { cursor = nextWorkingTime(cursor, config); } } return cursor; }