diff --git a/apps/client/src/common/utils/__tests__/time.test.ts b/apps/client/src/common/utils/__tests__/time.test.ts index beb195e5a..33edde1a6 100644 --- a/apps/client/src/common/utils/__tests__/time.test.ts +++ b/apps/client/src/common/utils/__tests__/time.test.ts @@ -1,3 +1,6 @@ +import { OffsetMode } from 'ontime-types'; +import { dayInMs } from 'ontime-utils'; + import { calculateTimeUntilStart, formatTime, nowInMillis } from '../time'; describe('nowInMillis()', () => { @@ -40,81 +43,251 @@ describe('formatTime()', () => { }); describe('calculateTimeUntilStart()', () => { - test('ontime', () => { + describe('Absolute', () => { + test('ontime', () => { + const test = { + timeStart: 100, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 0, + clock: 90, + offset: 0, + offsetMode: OffsetMode.Absolute, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(10); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10); + }); + + test('running behind', () => { + const test = { + timeStart: 100, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 0, + clock: 90, + offset: -20, + offsetMode: OffsetMode.Absolute, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(30); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(30); + }); + + test('running ahead', () => { + const test = { + timeStart: 100, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 0, + clock: 80, + offset: 10, + offsetMode: OffsetMode.Absolute, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); // <-- when running ahead the unlinked timer stays put + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10); + }); + + test('running behind with enough gaps', () => { + const test = { + timeStart: 100, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 20, + clock: 50, + offset: -20, + offsetMode: OffsetMode.Absolute, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(50); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible + }); + + test('running behind with to little gaps', () => { + const test = { + timeStart: 100, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 10, + clock: 50, + offset: -20, + offsetMode: OffsetMode.Absolute, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(60); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible + }); + }); + + describe('Relative', () => { + test('Basic test', () => { + const test = { + timeStart: 0, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 0, + clock: 100, + actualStart: 100, + plannedStart: 0, + offset: 0, // relative + // offset: -100, // absolute + offsetMode: OffsetMode.Relative, + }; + + const timeStartEvent2 = 10; + const timeStartEvent3 = 20; + + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: true })).toBe(10); + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: true })).toBe(20); + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: false })).toBe(10); + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: false })).toBe(20); + + test.clock = 105; + + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: true })).toBe(5); + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: true })).toBe(15); + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: false })).toBe(5); + expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: false })).toBe(15); + }); + + test('Test gaps', () => { + const test = { + timeStart: 20, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 10, + clock: 100, + actualStart: 100, + plannedStart: 0, + offset: 0, // relative + // offset: -100, // absolute + offsetMode: OffsetMode.Relative, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(20); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); + + test.clock = 105; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(15); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(15); + }); + + test('Test added/remove time', () => { + const test = { + timeStart: 20, + dayOffset: 0, + delay: 0, + currentDay: 0, + totalGap: 0, + clock: 100, + actualStart: 100, + plannedStart: 0, + offset: 0, // relative + // offset: -100, // absolute + offsetMode: OffsetMode.Relative, + }; + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(20); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); + + test.offset = 5; // remove 5 with addtime - we are ahead of time + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(15); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); // unlocked evets will stay on schedule + + test.offset = -5; // add 5 with addtime - we are behind + + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(25); + expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(25); + }); + + test('Test next day', () => { + const test = { + // timeStart: 10, + // dayOffset: 0, + delay: 0, + currentDay: 0, + // totalGap: 0, + clock: 100, + actualStart: 100, + plannedStart: 0, + offset: 0, // relative + // offset: -100, // absolute + offsetMode: OffsetMode.Relative, + }; + + expect( + calculateTimeUntilStart({ + ...test, + timeStart: 10, + dayOffset: 0, + totalGap: 0, + isLinkedToLoaded: true, + }), + ).toBe(10); + expect( + calculateTimeUntilStart({ + ...test, + timeStart: 10, + dayOffset: 0, + totalGap: 0, + isLinkedToLoaded: false, + }), + ).toBe(10); + + expect( + calculateTimeUntilStart({ + ...test, + timeStart: 0, + dayOffset: 1, + totalGap: dayInMs - 20, + isLinkedToLoaded: false, + }), + ).toBe(dayInMs); + + test.clock = 200; + + expect( + calculateTimeUntilStart({ + ...test, + timeStart: 0, + dayOffset: 1, + totalGap: dayInMs - 20, + isLinkedToLoaded: false, + }), + ).toBe(dayInMs - 100); + }); + }); + + test('Test overlap', () => { const test = { - timeStart: 100, + // timeStart: 20, dayOffset: 0, delay: 0, currentDay: 0, - totalGap: 0, - clock: 90, - offset: 0, + // totalGap: 10, + clock: 100, + actualStart: 100, + plannedStart: 0, + offset: 0, // relative + // offset: -100, // absolute + offsetMode: OffsetMode.Relative, + isLinkedToLoaded: false, }; - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(10); - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10); + expect(calculateTimeUntilStart({ ...test, timeStart: 5, totalGap: -5 })).toBe(10); + + test.clock = 105; }); - - test('running behind', () => { - const test = { - timeStart: 100, - dayOffset: 0, - delay: 0, - currentDay: 0, - totalGap: 0, - clock: 90, - offset: -20, - }; - - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(30); - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(30); - }); - - test('running ahead', () => { - const test = { - timeStart: 100, - dayOffset: 0, - delay: 0, - currentDay: 0, - totalGap: 0, - clock: 80, - offset: 10, - }; - - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); // <-- when running ahead the unlinked timer stays put - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10); - }); - - test('running behind with enough gaps', () => { - const test = { - timeStart: 100, - dayOffset: 0, - delay: 0, - currentDay: 0, - totalGap: 20, - clock: 50, - offset: -20, - }; - - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(50); - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible - }); - - test('running behind with to little gaps', () => { - const test = { - timeStart: 100, - dayOffset: 0, - delay: 0, - currentDay: 0, - totalGap: 10, - clock: 50, - offset: -20, - }; - - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(60); - expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible - }); - //TODO: more indepth testing, // including day offset handling // and more? diff --git a/apps/client/src/common/utils/time.ts b/apps/client/src/common/utils/time.ts index 4c5ee9ed7..a5f5f99e5 100644 --- a/apps/client/src/common/utils/time.ts +++ b/apps/client/src/common/utils/time.ts @@ -1,4 +1,4 @@ -import { MaybeNumber, OntimeEvent, Settings, TimeFormat } from 'ontime-types'; +import { MaybeNumber, OffsetMode, OntimeEvent, Settings, TimeFormat } from 'ontime-types'; import { dayInMs, formatFromMillis, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils'; import { FORMAT_12, FORMAT_24 } from '../../viewerConfig'; @@ -138,6 +138,7 @@ export function useTimeUntilStart( data: Pick & { totalGap: number; isLinkedToLoaded: boolean; + offsetMode: OffsetMode; }, ): number { const { offset, clock, currentDay } = useTimeUntilData(); @@ -160,9 +161,24 @@ export function calculateTimeUntilStart( isLinkedToLoaded: boolean; clock: number; offset: number; + offsetMode: OffsetMode; + actualStart?: number; + plannedStart?: number; }, ): number { - const { timeStart, dayOffset, currentDay, totalGap, isLinkedToLoaded, clock, offset, delay } = data; + const { + timeStart, + dayOffset, + currentDay, + totalGap, + isLinkedToLoaded, + clock, + offset, + delay, + offsetMode, + actualStart, + plannedStart, + } = data; //How many days from the currently running event to this one const relativeDayOffset = dayOffset - currentDay; @@ -172,20 +188,25 @@ export function calculateTimeUntilStart( //The normalised start time of this event relative to the currently running event const normalisedTimeStart = delayedStart + relativeDayOffset * dayInMs; - const offsetTimestart = normalisedTimeStart - offset; - const offsetTimeUntil = offsetTimestart - clock; + let relativeStartOffset = 0; + + if (offsetMode === OffsetMode.Relative) { + assert(actualStart !== undefined); + assert(plannedStart !== undefined); + relativeStartOffset = actualStart - plannedStart; + } + + const scheduledTimeUntil = normalisedTimeStart - clock + relativeStartOffset; + + const offsetTimeUntil = scheduledTimeUntil - offset; if (isLinkedToLoaded) { //if we are directly linked back to the loaded event we just follow the offset return offsetTimeUntil; } - const scheduledTimeUntil = normalisedTimeStart - clock; - - const isAheadOfSchedule = offset >= 0; const gapsCanCompensadeForOffset = totalGap + offset >= 0; - - if (isAheadOfSchedule || gapsCanCompensadeForOffset) { + if (gapsCanCompensadeForOffset) { // if we are ahead of schedule or the gap can compensate for the amount we are behind then expect to start at the scheduled time return scheduledTimeUntil; }