mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-27 09:59:08 +00:00
refactor: unify logic
This commit is contained in:
committed by
Carlos Valente
parent
25c7915cf7
commit
f5936e5254
@@ -101,7 +101,7 @@ export const formatTime = (
|
|||||||
* @param duration
|
* @param duration
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function formatDuration(duration: number): string {
|
export function formatDuration(duration: number, hideSeconds = true): string {
|
||||||
// durations should never be negative, we handle it here to flag if there is an issue in future
|
// durations should never be negative, we handle it here to flag if there is an issue in future
|
||||||
if (duration <= 0) {
|
if (duration <= 0) {
|
||||||
return '0h 0m';
|
return '0h 0m';
|
||||||
@@ -111,10 +111,17 @@ export function formatDuration(duration: number): string {
|
|||||||
const minutes = Math.floor((duration % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE);
|
const minutes = Math.floor((duration % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE);
|
||||||
let result = '';
|
let result = '';
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
result += `${hours}h `;
|
result += `${hours}h`;
|
||||||
}
|
}
|
||||||
if (minutes > 0) {
|
if (minutes > 0) {
|
||||||
result += `${minutes}m`;
|
result += `${minutes}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hideSeconds) {
|
||||||
|
const seconds = Math.floor((duration % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND);
|
||||||
|
if (seconds > 0) {
|
||||||
|
result += `${seconds}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { MaybeNumber } from 'ontime-types';
|
import { MaybeNumber } from 'ontime-types';
|
||||||
import { checkIsNextDay, dayInMs, millisToString, removeLeadingZero, removeTrailingZero } from 'ontime-utils';
|
import {
|
||||||
|
calculateDuration,
|
||||||
|
checkIsNextDay,
|
||||||
|
dayInMs,
|
||||||
|
getTimeFromPrevious,
|
||||||
|
millisToString,
|
||||||
|
removeTrailingZero,
|
||||||
|
} from 'ontime-utils';
|
||||||
|
|
||||||
|
import { formatDuration } from '../../../common/utils/time';
|
||||||
|
|
||||||
export function formatDelay(timeStart: number, delay: number): string | undefined {
|
export function formatDelay(timeStart: number, delay: number): string | undefined {
|
||||||
if (!delay) return;
|
if (!delay) return;
|
||||||
@@ -18,22 +27,20 @@ export function formatOverlap(
|
|||||||
const noPreviousElement = previousEnd === null || previousStart === null;
|
const noPreviousElement = previousEnd === null || previousStart === null;
|
||||||
if (noPreviousElement) return;
|
if (noPreviousElement) return;
|
||||||
|
|
||||||
const timeFromPrevious = previousEnd - timeStart;
|
const normalisedDuration = calculateDuration(previousStart, previousEnd);
|
||||||
|
const timeFromPrevious = getTimeFromPrevious(timeStart, previousStart, previousEnd, normalisedDuration);
|
||||||
if (timeFromPrevious === 0) return;
|
if (timeFromPrevious === 0) return;
|
||||||
|
|
||||||
const previousCrossMidnight = previousStart > previousEnd;
|
if (checkIsNextDay(previousStart, timeStart, normalisedDuration)) {
|
||||||
const isNextDay = previousCrossMidnight
|
const previousCrossMidnight = previousStart > previousEnd;
|
||||||
? previousEnd === 0 || checkIsNextDay(previousEnd, timeStart, previousEnd - previousStart) // exception for when previousEnd is precisely midnight
|
const normalisedPreviousEnd = previousCrossMidnight ? previousEnd + dayInMs : previousEnd;
|
||||||
: checkIsNextDay(previousStart, timeStart, previousEnd - previousStart);
|
|
||||||
const correctedPreviousEnd = previousCrossMidnight ? previousEnd + dayInMs : previousEnd;
|
|
||||||
|
|
||||||
if (isNextDay) {
|
const gap = dayInMs - normalisedPreviousEnd + timeStart;
|
||||||
const gap = dayInMs - correctedPreviousEnd + timeStart;
|
|
||||||
if (gap === 0) return;
|
if (gap === 0) return;
|
||||||
const gapString = removeLeadingZero(millisToString(Math.abs(gap)));
|
const gapString = formatDuration(Math.abs(gap), false);
|
||||||
return `Gap ${gapString} (next day)`;
|
return `Gap ${gapString} (next day)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const overlapString = removeLeadingZero(millisToString(Math.abs(timeFromPrevious)));
|
const overlapString = formatDuration(Math.abs(timeFromPrevious), false);
|
||||||
return `${timeFromPrevious > 0 ? 'Overlap' : 'Gap'} ${overlapString}`;
|
return `${timeFromPrevious < 0 ? 'Overlap' : 'Gap'} ${overlapString}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe('formatOverlap()', () => {
|
|||||||
const previousEnd = 60000; // 1 min
|
const previousEnd = 60000; // 1 min
|
||||||
const timeStart = 30000; // 30 sec
|
const timeStart = 30000; // 30 sec
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
expect(result).toEqual('Overlap 0:30');
|
expect(result).toEqual('Overlap 30s');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bug #949 recognises an overlap between two times', () => {
|
it('bug #949 recognises an overlap between two times', () => {
|
||||||
@@ -25,7 +25,7 @@ describe('formatOverlap()', () => {
|
|||||||
const previousEnd = 48600000; // 13:30:00
|
const previousEnd = 48600000; // 13:30:00
|
||||||
const timeStart = 48300000; // 13:25:00
|
const timeStart = 48300000; // 13:25:00
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
expect(result).toEqual('Overlap 5:00');
|
expect(result).toEqual('Overlap 5m');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles events the day after, without overlap', () => {
|
it('handles events the day after, without overlap', () => {
|
||||||
@@ -33,7 +33,7 @@ describe('formatOverlap()', () => {
|
|||||||
const previousEnd = 12 * MILLIS_PER_HOUR;
|
const previousEnd = 12 * MILLIS_PER_HOUR;
|
||||||
const timeStart = 6 * MILLIS_PER_HOUR;
|
const timeStart = 6 * MILLIS_PER_HOUR;
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
expect(result).toBe('Gap 18:00:00 (next day)');
|
expect(result).toBe('Gap 18h (next day)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles events the day after, with gap', () => {
|
it('handles events the day after, with gap', () => {
|
||||||
@@ -41,7 +41,7 @@ describe('formatOverlap()', () => {
|
|||||||
const previousEnd = 23 * MILLIS_PER_HOUR;
|
const previousEnd = 23 * MILLIS_PER_HOUR;
|
||||||
const timeStart = 9 * MILLIS_PER_HOUR;
|
const timeStart = 9 * MILLIS_PER_HOUR;
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
expect(result).toBe('Gap 10:00:00 (next day)');
|
expect(result).toBe('Gap 10h (next day)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles events the day after, with previous ending at midnight', () => {
|
it('handles events the day after, with previous ending at midnight', () => {
|
||||||
@@ -49,7 +49,7 @@ describe('formatOverlap()', () => {
|
|||||||
const previousEnd = 0; // 00:00:00
|
const previousEnd = 0; // 00:00:00
|
||||||
const timeStart = 1 * MILLIS_PER_HOUR; // 01:00:00
|
const timeStart = 1 * MILLIS_PER_HOUR; // 01:00:00
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
expect(result).toBe('Gap 01:00:00 (next day)');
|
expect(result).toBe('Gap 1h (next day)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles events the day after, with previous ending over midnight', () => {
|
it('handles events the day after, with previous ending over midnight', () => {
|
||||||
@@ -57,6 +57,6 @@ describe('formatOverlap()', () => {
|
|||||||
const previousEnd = 1 * MILLIS_PER_HOUR;
|
const previousEnd = 1 * MILLIS_PER_HOUR;
|
||||||
const timeStart = 2 * MILLIS_PER_HOUR;
|
const timeStart = 2 * MILLIS_PER_HOUR;
|
||||||
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
const result = formatOverlap(previousStart, previousEnd, timeStart);
|
||||||
expect(result).toBe('Gap 01:00:00');
|
expect(result).toBe('Gap 1h');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export function generate(
|
|||||||
|
|
||||||
const timeFromPrevious: number = getTimeFromPrevious(
|
const timeFromPrevious: number = getTimeFromPrevious(
|
||||||
currentEntry.timeStart,
|
currentEntry.timeStart,
|
||||||
currentEntry.timeEnd,
|
|
||||||
lastEntry?.timeStart,
|
lastEntry?.timeStart,
|
||||||
lastEntry?.timeEnd,
|
lastEntry?.timeEnd,
|
||||||
lastEntry?.duration,
|
lastEntry?.duration,
|
||||||
@@ -157,13 +156,6 @@ export function generate(
|
|||||||
totalDelay += currentEntry.duration;
|
totalDelay += currentEntry.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
|
||||||
DEV: {
|
|
||||||
if (totalDuration < 0) {
|
|
||||||
throw new Error('rundownCache.generate: invalid data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add id to order
|
// add id to order
|
||||||
order.push(currentEntry.id);
|
order.push(currentEntry.id);
|
||||||
// add entry to rundown
|
// add entry to rundown
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { checkIsNextDay } from './checkIsNextDay';
|
||||||
|
import { MILLIS_PER_HOUR } from './conversionUtils';
|
||||||
|
|
||||||
|
describe('checkIsNextDay', () => {
|
||||||
|
it('returns false if the previous event duration is 0', () => {
|
||||||
|
const previousStart = 0;
|
||||||
|
const previousDuration = 0;
|
||||||
|
const timeStart = 0;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if event starts after one before', () => {
|
||||||
|
const previousStart = 10;
|
||||||
|
const previousDuration = 2;
|
||||||
|
const timeStart = 11;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if event starts after one before', () => {
|
||||||
|
const previousStart = 10;
|
||||||
|
const previousDuration = 2;
|
||||||
|
const timeStart = 9;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if event starts at the same time as one before', () => {
|
||||||
|
const previousStart = 10;
|
||||||
|
const previousDuration = 2;
|
||||||
|
const timeStart = 10;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should account for an event that crossed midnight', () => {
|
||||||
|
const previousStart = 20 * MILLIS_PER_HOUR;
|
||||||
|
const previousDuration = 6 * MILLIS_PER_HOUR; // event finished at 02:00:00
|
||||||
|
const timeStart = 1 * MILLIS_PER_HOUR;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should account for an event that crossed midnight and there is a gap', () => {
|
||||||
|
const previousStart = 23 * MILLIS_PER_HOUR;
|
||||||
|
const timeStart = 2 * MILLIS_PER_HOUR;
|
||||||
|
const previousDuration = 2 * MILLIS_PER_HOUR;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should account for an event that crossed midnight with no overlaps', () => {
|
||||||
|
const previousStart = 20 * MILLIS_PER_HOUR;
|
||||||
|
const previousDuration = 6 * MILLIS_PER_HOUR; // event finished at 02:00:00
|
||||||
|
const timeStart = 19 * MILLIS_PER_HOUR;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should account for an event that finishes exactly at midnight', () => {
|
||||||
|
const previousStart = 23 * MILLIS_PER_HOUR;
|
||||||
|
const previousDuration = 1 * MILLIS_PER_HOUR;
|
||||||
|
const timeStart = 2 * MILLIS_PER_HOUR;
|
||||||
|
expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +1,40 @@
|
|||||||
|
import { dayInMs } from './conversionUtils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function checks whether a given event is the day after from its predecessor
|
* Utility function checks whether a given event is the day after from its predecessor
|
||||||
* We consider an event to be the day after, if it begins before the start of the previous
|
* We consider an event to be the day after, if it begins before the start of the previous
|
||||||
* @example day after
|
* @example day after
|
||||||
* 09:00 - 10:00
|
* 09:00 - 10:00
|
||||||
* 08:00 - 10:30
|
* 08:00 - 10:30
|
||||||
|
* @example day after
|
||||||
|
* 23:00 - 00:00
|
||||||
|
* 02:00 - 03:00
|
||||||
* @example same day
|
* @example same day
|
||||||
* 09:00 - 10:00
|
* 09:00 - 10:00
|
||||||
* 09:30 - 10:30
|
* 09:30 - 10:30
|
||||||
|
* @example same day, but previous crosses midnight
|
||||||
|
* 23:00 - 01:00
|
||||||
|
* 02:00 - 03:00
|
||||||
|
* @example same day, but previous crosses midnight (with overlap)
|
||||||
|
* 22:00 - 02:00
|
||||||
|
* 01:00 - 03:00
|
||||||
*/
|
*/
|
||||||
export function checkIsNextDay(previousStart: number, timeStart: number, previousDuration: number): boolean {
|
export function checkIsNextDay(previousStart: number, timeStart: number, previousDuration: number): boolean {
|
||||||
return previousDuration === 0 ? false : timeStart <= previousStart;
|
if (previousDuration === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeStart <= previousStart) {
|
||||||
|
const normalisedPreviousEnd = previousStart + previousDuration;
|
||||||
|
if (normalisedPreviousEnd === dayInMs) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// handle exception for an event that finishes exactly at midnight
|
||||||
|
if (normalisedPreviousEnd > dayInMs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ describe('getTimeFromPrevious', () => {
|
|||||||
const previousEnd = 71700000; // 19:55
|
const previousEnd = 71700000; // 19:55
|
||||||
const previousDuration = 2100000; // 35 minutes
|
const previousDuration = 2100000; // 35 minutes
|
||||||
const currentStart = 75600000; // 21:00
|
const currentStart = 75600000; // 21:00
|
||||||
const currentEnd = 81000000; // 22:30
|
|
||||||
const expected = 75600000 - 71700000; // current start - previousEnd
|
const expected = 75600000 - 71700000; // current start - previousEnd
|
||||||
|
|
||||||
expect(getTimeFromPrevious(currentStart, currentEnd, previousStart, previousEnd, previousDuration)).toBe(expected);
|
expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accounts for partially overlapping events', () => {
|
it('accounts for partially overlapping events', () => {
|
||||||
@@ -18,10 +17,9 @@ describe('getTimeFromPrevious', () => {
|
|||||||
const previousEnd = 12;
|
const previousEnd = 12;
|
||||||
const previousDuration = 2;
|
const previousDuration = 2;
|
||||||
const currentStart = 11;
|
const currentStart = 11;
|
||||||
const currentEnd = 12;
|
|
||||||
const expected = -(previousEnd - currentStart);
|
const expected = -(previousEnd - currentStart);
|
||||||
|
|
||||||
expect(getTimeFromPrevious(currentStart, currentEnd, previousStart, previousEnd, previousDuration)).toBe(expected);
|
expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accounts for events that are fully contained', () => {
|
it('accounts for events that are fully contained', () => {
|
||||||
@@ -29,10 +27,9 @@ describe('getTimeFromPrevious', () => {
|
|||||||
const previousEnd = 16;
|
const previousEnd = 16;
|
||||||
const previousDuration = 8;
|
const previousDuration = 8;
|
||||||
const currentStart = 10;
|
const currentStart = 10;
|
||||||
const currentEnd = 15;
|
|
||||||
const expected = -(previousEnd - currentStart);
|
const expected = -(previousEnd - currentStart);
|
||||||
|
|
||||||
expect(getTimeFromPrevious(currentStart, currentEnd, previousStart, previousEnd, previousDuration)).toBe(expected);
|
expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fully overlapping events are the next day', () => {
|
it('fully overlapping events are the next day', () => {
|
||||||
@@ -40,9 +37,28 @@ describe('getTimeFromPrevious', () => {
|
|||||||
const previousEnd = 12 * MILLIS_PER_HOUR;
|
const previousEnd = 12 * MILLIS_PER_HOUR;
|
||||||
const previousDuration = previousEnd - previousStart;
|
const previousDuration = previousEnd - previousStart;
|
||||||
const currentStart = 10 * MILLIS_PER_HOUR;
|
const currentStart = 10 * MILLIS_PER_HOUR;
|
||||||
const currentEnd = 12 * MILLIS_PER_HOUR;
|
|
||||||
const expected = dayInMs - previousDuration;
|
const expected = dayInMs - previousDuration;
|
||||||
|
|
||||||
expect(getTimeFromPrevious(currentStart, currentEnd, previousStart, previousEnd, previousDuration)).toBe(expected);
|
expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accounts for events that are the day after', () => {
|
||||||
|
const previousStart = 20 * MILLIS_PER_HOUR;
|
||||||
|
const previousEnd = 23 * MILLIS_PER_HOUR;
|
||||||
|
const previousDuration = 3 * MILLIS_PER_HOUR;
|
||||||
|
const currentStart = 22 * MILLIS_PER_HOUR;
|
||||||
|
const expected = -MILLIS_PER_HOUR; // (previousEnd - currentStart);
|
||||||
|
|
||||||
|
expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accounts for events that cross midnight', () => {
|
||||||
|
const previousStart = 20 * MILLIS_PER_HOUR;
|
||||||
|
const previousEnd = 2 * MILLIS_PER_HOUR;
|
||||||
|
const previousDuration = 6 * MILLIS_PER_HOUR;
|
||||||
|
const currentStart = 1 * MILLIS_PER_HOUR;
|
||||||
|
const expected = -MILLIS_PER_HOUR; // (previousEnd - currentStart);
|
||||||
|
|
||||||
|
expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ import { dayInMs } from './conversionUtils.js';
|
|||||||
*/
|
*/
|
||||||
export function getTimeFromPrevious(
|
export function getTimeFromPrevious(
|
||||||
currentStart: number,
|
currentStart: number,
|
||||||
currentEnd: number,
|
|
||||||
previousStart?: number,
|
previousStart?: number,
|
||||||
previousEnd?: number,
|
previousEnd?: number,
|
||||||
previousDuration?: number,
|
previousDuration?: number,
|
||||||
): number {
|
): number {
|
||||||
// there is no previous event
|
// there is no previous event
|
||||||
if (previousStart == null || previousEnd == null || previousDuration == null) {
|
if (previousStart === undefined || previousEnd === undefined || previousDuration === undefined) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ export function getTimeFromPrevious(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// event overlaps with previous
|
// event overlaps with previous
|
||||||
// TODO: account for midnight roll
|
|
||||||
const overlap = previousEnd - currentStart;
|
const overlap = previousEnd - currentStart;
|
||||||
if (overlap > 0) {
|
if (overlap > 0) {
|
||||||
// time is a negative number indicating the amount of overlap
|
// time is a negative number indicating the amount of overlap
|
||||||
|
|||||||
Reference in New Issue
Block a user