From f5936e52545a5464b79bdd7560ba9a71bcd06a0a Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Tue, 27 Aug 2024 08:57:20 +0200 Subject: [PATCH] refactor: unify logic --- apps/client/src/common/utils/time.ts | 11 +++- .../rundown/event-block/EventBlock.utils.ts | 31 ++++++---- .../__tests__/EventBlock.utils.test.ts | 12 ++-- .../services/rundown-service/rundownCache.ts | 8 --- .../src/date-utils/checkIsNextDay.test.ts | 60 +++++++++++++++++++ .../utils/src/date-utils/checkIsNextDay.ts | 29 ++++++++- .../date-utils/getTimeFromPrevious.test.ts | 32 +++++++--- .../src/date-utils/getTimeFromPrevious.ts | 4 +- 8 files changed, 147 insertions(+), 40 deletions(-) create mode 100644 packages/utils/src/date-utils/checkIsNextDay.test.ts diff --git a/apps/client/src/common/utils/time.ts b/apps/client/src/common/utils/time.ts index 8f60a3f90..de4283e1c 100644 --- a/apps/client/src/common/utils/time.ts +++ b/apps/client/src/common/utils/time.ts @@ -101,7 +101,7 @@ export const formatTime = ( * @param duration * @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 if (duration <= 0) { return '0h 0m'; @@ -111,10 +111,17 @@ export function formatDuration(duration: number): string { const minutes = Math.floor((duration % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE); let result = ''; if (hours > 0) { - result += `${hours}h `; + result += `${hours}h`; } if (minutes > 0) { result += `${minutes}m`; } + + if (!hideSeconds) { + const seconds = Math.floor((duration % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND); + if (seconds > 0) { + result += `${seconds}s`; + } + } return result; } diff --git a/apps/client/src/features/rundown/event-block/EventBlock.utils.ts b/apps/client/src/features/rundown/event-block/EventBlock.utils.ts index e42898419..9e3f8fd9a 100644 --- a/apps/client/src/features/rundown/event-block/EventBlock.utils.ts +++ b/apps/client/src/features/rundown/event-block/EventBlock.utils.ts @@ -1,5 +1,14 @@ 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 { if (!delay) return; @@ -18,22 +27,20 @@ export function formatOverlap( const noPreviousElement = previousEnd === null || previousStart === null; if (noPreviousElement) return; - const timeFromPrevious = previousEnd - timeStart; + const normalisedDuration = calculateDuration(previousStart, previousEnd); + const timeFromPrevious = getTimeFromPrevious(timeStart, previousStart, previousEnd, normalisedDuration); if (timeFromPrevious === 0) return; - const previousCrossMidnight = previousStart > previousEnd; - const isNextDay = previousCrossMidnight - ? previousEnd === 0 || checkIsNextDay(previousEnd, timeStart, previousEnd - previousStart) // exception for when previousEnd is precisely midnight - : checkIsNextDay(previousStart, timeStart, previousEnd - previousStart); - const correctedPreviousEnd = previousCrossMidnight ? previousEnd + dayInMs : previousEnd; + if (checkIsNextDay(previousStart, timeStart, normalisedDuration)) { + const previousCrossMidnight = previousStart > previousEnd; + const normalisedPreviousEnd = previousCrossMidnight ? previousEnd + dayInMs : previousEnd; - if (isNextDay) { - const gap = dayInMs - correctedPreviousEnd + timeStart; + const gap = dayInMs - normalisedPreviousEnd + timeStart; if (gap === 0) return; - const gapString = removeLeadingZero(millisToString(Math.abs(gap))); + const gapString = formatDuration(Math.abs(gap), false); return `Gap ${gapString} (next day)`; } - const overlapString = removeLeadingZero(millisToString(Math.abs(timeFromPrevious))); - return `${timeFromPrevious > 0 ? 'Overlap' : 'Gap'} ${overlapString}`; + const overlapString = formatDuration(Math.abs(timeFromPrevious), false); + return `${timeFromPrevious < 0 ? 'Overlap' : 'Gap'} ${overlapString}`; } diff --git a/apps/client/src/features/rundown/event-block/__tests__/EventBlock.utils.test.ts b/apps/client/src/features/rundown/event-block/__tests__/EventBlock.utils.test.ts index cc5982646..2ade46bf8 100644 --- a/apps/client/src/features/rundown/event-block/__tests__/EventBlock.utils.test.ts +++ b/apps/client/src/features/rundown/event-block/__tests__/EventBlock.utils.test.ts @@ -17,7 +17,7 @@ describe('formatOverlap()', () => { const previousEnd = 60000; // 1 min const timeStart = 30000; // 30 sec 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', () => { @@ -25,7 +25,7 @@ describe('formatOverlap()', () => { const previousEnd = 48600000; // 13:30:00 const timeStart = 48300000; // 13:25:00 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', () => { @@ -33,7 +33,7 @@ describe('formatOverlap()', () => { const previousEnd = 12 * MILLIS_PER_HOUR; const timeStart = 6 * MILLIS_PER_HOUR; 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', () => { @@ -41,7 +41,7 @@ describe('formatOverlap()', () => { const previousEnd = 23 * MILLIS_PER_HOUR; const timeStart = 9 * MILLIS_PER_HOUR; 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', () => { @@ -49,7 +49,7 @@ describe('formatOverlap()', () => { const previousEnd = 0; // 00:00:00 const timeStart = 1 * MILLIS_PER_HOUR; // 01:00:00 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', () => { @@ -57,6 +57,6 @@ describe('formatOverlap()', () => { const previousEnd = 1 * MILLIS_PER_HOUR; const timeStart = 2 * MILLIS_PER_HOUR; const result = formatOverlap(previousStart, previousEnd, timeStart); - expect(result).toBe('Gap 01:00:00'); + expect(result).toBe('Gap 1h'); }); }); diff --git a/apps/server/src/services/rundown-service/rundownCache.ts b/apps/server/src/services/rundown-service/rundownCache.ts index 3e78bfba4..77381382e 100644 --- a/apps/server/src/services/rundown-service/rundownCache.ts +++ b/apps/server/src/services/rundown-service/rundownCache.ts @@ -113,7 +113,6 @@ export function generate( const timeFromPrevious: number = getTimeFromPrevious( currentEntry.timeStart, - currentEntry.timeEnd, lastEntry?.timeStart, lastEntry?.timeEnd, lastEntry?.duration, @@ -157,13 +156,6 @@ export function generate( 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 order.push(currentEntry.id); // add entry to rundown diff --git a/packages/utils/src/date-utils/checkIsNextDay.test.ts b/packages/utils/src/date-utils/checkIsNextDay.test.ts new file mode 100644 index 000000000..e71c2a81a --- /dev/null +++ b/packages/utils/src/date-utils/checkIsNextDay.test.ts @@ -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(); + }); +}); diff --git a/packages/utils/src/date-utils/checkIsNextDay.ts b/packages/utils/src/date-utils/checkIsNextDay.ts index 6e337dd36..93a0492c6 100644 --- a/packages/utils/src/date-utils/checkIsNextDay.ts +++ b/packages/utils/src/date-utils/checkIsNextDay.ts @@ -1,13 +1,40 @@ +import { dayInMs } from './conversionUtils.js'; + /** * 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 * @example day after * 09:00 - 10:00 * 08:00 - 10:30 + * @example day after + * 23:00 - 00:00 + * 02:00 - 03:00 * @example same day * 09:00 - 10:00 * 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 { - 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; } diff --git a/packages/utils/src/date-utils/getTimeFromPrevious.test.ts b/packages/utils/src/date-utils/getTimeFromPrevious.test.ts index 944a862cb..4fc37443b 100644 --- a/packages/utils/src/date-utils/getTimeFromPrevious.test.ts +++ b/packages/utils/src/date-utils/getTimeFromPrevious.test.ts @@ -7,10 +7,9 @@ describe('getTimeFromPrevious', () => { const previousEnd = 71700000; // 19:55 const previousDuration = 2100000; // 35 minutes const currentStart = 75600000; // 21:00 - const currentEnd = 81000000; // 22:30 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', () => { @@ -18,10 +17,9 @@ describe('getTimeFromPrevious', () => { const previousEnd = 12; const previousDuration = 2; const currentStart = 11; - const currentEnd = 12; 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', () => { @@ -29,10 +27,9 @@ describe('getTimeFromPrevious', () => { const previousEnd = 16; const previousDuration = 8; const currentStart = 10; - const currentEnd = 15; 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', () => { @@ -40,9 +37,28 @@ describe('getTimeFromPrevious', () => { const previousEnd = 12 * MILLIS_PER_HOUR; const previousDuration = previousEnd - previousStart; const currentStart = 10 * MILLIS_PER_HOUR; - const currentEnd = 12 * MILLIS_PER_HOUR; 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); }); }); diff --git a/packages/utils/src/date-utils/getTimeFromPrevious.ts b/packages/utils/src/date-utils/getTimeFromPrevious.ts index e0dad100d..7cdd521dd 100644 --- a/packages/utils/src/date-utils/getTimeFromPrevious.ts +++ b/packages/utils/src/date-utils/getTimeFromPrevious.ts @@ -7,13 +7,12 @@ import { dayInMs } from './conversionUtils.js'; */ export function getTimeFromPrevious( currentStart: number, - currentEnd: number, previousStart?: number, previousEnd?: number, previousDuration?: number, ): number { // there is no previous event - if (previousStart == null || previousEnd == null || previousDuration == null) { + if (previousStart === undefined || previousEnd === undefined || previousDuration === undefined) { return 0; } @@ -35,7 +34,6 @@ export function getTimeFromPrevious( } // event overlaps with previous - // TODO: account for midnight roll const overlap = previousEnd - currentStart; if (overlap > 0) { // time is a negative number indicating the amount of overlap