import type { OntimeEvent } from 'ontime-types'; import { TimeStrategy } from 'ontime-types'; import { dayInMs } from '../date-utils/conversionUtils.js'; import { validateTimeStrategy } from '../validate-events/validateEvent.js'; export function getLinkedTimes( target: OntimeEvent, source: OntimeEvent, ): { timeStart: number; duration: number; timeEnd: number } { const lockEnd = target.timeStrategy === TimeStrategy.LockEnd; const lockDuration = target.timeStrategy === TimeStrategy.LockDuration; const newStart = source.timeEnd; const timePatch = { timeStart: newStart, timeEnd: lockEnd ? target.timeEnd : calculateEnd(newStart, target.duration), duration: lockDuration ? target.duration : calculateDuration(newStart, target.timeEnd), }; return timePatch; } function inferTimes( _start?: unknown, _end?: unknown, _duration?: unknown, ): { timeStart: number; duration: number; timeEnd: number; timeStrategy: TimeStrategy } { const timeStart = convertToInteger(_start); const timeEnd = convertToInteger(_end); const duration = convertToInteger(_duration); // TODO: prevent overflow if (_start != null && _end != null) { // Case 1. if we have start and end, duration must be derived return { timeStart, duration: calculateDuration(timeStart, timeEnd), timeEnd, timeStrategy: _duration != null ? TimeStrategy.LockDuration : TimeStrategy.LockEnd, }; } if (_start == null && _end == null) { if (_duration == null) { // Case 2. no valid times were given return { timeStart: 0, duration: 0, timeEnd: 0, timeStrategy: TimeStrategy.LockDuration }; } // Case 3. we have a duration and infer the rest return { timeStart, duration, timeEnd: duration, timeStrategy: TimeStrategy.LockDuration }; } if (_start != null) { // Case 5. with only start, we can calculate the rest return { timeStart, duration, timeEnd: (timeStart + duration) % dayInMs, timeStrategy: TimeStrategy.LockDuration }; } if (_end != null) { // Case 6. with only end, we can calculate the rest return { timeStart: timeEnd - duration, duration, timeEnd, timeStrategy: _duration != null ? TimeStrategy.LockDuration : TimeStrategy.LockEnd, }; } // we should have covered all cases return { timeStart, duration, timeEnd, timeStrategy: TimeStrategy.LockDuration }; } /** * Ensures the time input variables are valid in relationship to each other * Infers values if necessary * @param _start * @param _end * @param _duration */ export function validateTimes( _start?: unknown, _end?: unknown, _duration?: unknown, _strategy?: TimeStrategy, ): { timeStart: number; duration: number; timeEnd: number; timeStrategy: TimeStrategy } { if (_strategy == null) { // if no strategy is given we infer it from given parameters return inferTimes(_start, _end, _duration); } const timeStrategy = validateTimeStrategy(_strategy); const timeStart = convertToInteger(_start); let timeEnd = convertToInteger(_end); let duration = convertToInteger(_duration); if (timeStrategy === TimeStrategy.LockEnd) { duration = calculateDuration(timeStart, timeEnd); } else { timeEnd = calculateEnd(timeStart, duration); } return { timeStart, duration, timeEnd, timeStrategy }; } /** * @description calculates event duration considering midnight * @param {number} timeStart * @param {number} timeEnd * @returns {number} */ export function calculateDuration(timeStart: number, timeEnd: number): number { // Durations must be positive if (timeEnd < timeStart) { return timeEnd + dayInMs - timeStart; } return timeEnd - timeStart; } export function calculateEnd(timeStart: number, duration: number): number { return (timeStart + duration) % dayInMs; } /** * Converts a given value to an int, returns 0 otherwise * @param value * number */ function convertToInteger(value: unknown): number { const result = Number(value); return isNaN(result) ? 0 : Math.floor(result); }