mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 19:03:47 +00:00
fix: issue where a countToEnd would lead to incorrect expected times (#2149)
* fix: issue where a count-to-end would lead to incorrect expected times * fix: include add time in overtime when countToEnd * fix: ui and server use same calculation for expected end
This commit is contained in:
committed by
GitHub
parent
a8c611911d
commit
2a890cf2b3
+142
-1
@@ -1,7 +1,7 @@
|
||||
import { Day, OffsetMode } from 'ontime-types';
|
||||
|
||||
import { MILLIS_PER_HOUR, dayInMs } from './conversionUtils';
|
||||
import { getExpectedStart } from './getExpectedStart';
|
||||
import { getExpectedEnd, getExpectedStart } from './getExpected';
|
||||
|
||||
describe('getExpectedStart()', () => {
|
||||
describe('Absolute offset mode', () => {
|
||||
@@ -315,3 +315,144 @@ describe('getExpectedStart()', () => {
|
||||
expect(getExpectedStart(testEvent, { ...testState, currentDay: 0 })).toBe(23 * MILLIS_PER_HOUR + 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExpectedEnd()', () => {
|
||||
const baseState = {
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
mode: OffsetMode.Absolute,
|
||||
actualStart: null,
|
||||
plannedStart: null,
|
||||
isLinkedToLoaded: true,
|
||||
};
|
||||
|
||||
test('a regular event ends at its expected start plus duration', () => {
|
||||
const testEvent = {
|
||||
timeStart: 100,
|
||||
duration: 50,
|
||||
delay: 0,
|
||||
dayOffset: 0 as Day,
|
||||
countToEnd: false,
|
||||
};
|
||||
|
||||
// on schedule
|
||||
const expectedStart0 = getExpectedStart(testEvent, { ...baseState, offset: 0 });
|
||||
expect(getExpectedEnd(testEvent, expectedStart0, baseState.currentDay)).toBe(150);
|
||||
|
||||
// running 20 behind pushes the end out
|
||||
const expectedStart20 = getExpectedStart(testEvent, { ...baseState, offset: 20 });
|
||||
expect(getExpectedEnd(testEvent, expectedStart20, baseState.currentDay)).toBe(170);
|
||||
});
|
||||
|
||||
test('a countToEnd event pins to the planned end while in overtime', () => {
|
||||
const testEvent = {
|
||||
timeStart: 100,
|
||||
duration: 50,
|
||||
delay: 0,
|
||||
dayOffset: 0 as Day,
|
||||
countToEnd: true,
|
||||
};
|
||||
|
||||
// overtime would otherwise push the end to 170, but countToEnd absorbs it and pins to 150
|
||||
const expectedStart = getExpectedStart(testEvent, { ...baseState, offset: 20 });
|
||||
expect(getExpectedEnd(testEvent, expectedStart, baseState.currentDay)).toBe(150);
|
||||
});
|
||||
|
||||
test('an overnight countToEnd event returns a normalised end', () => {
|
||||
// event starts at 23:00 and counts to 01:00 the next day -> duration spans midnight
|
||||
const timeStart = 23 * MILLIS_PER_HOUR;
|
||||
const duration = 2 * MILLIS_PER_HOUR;
|
||||
const testEvent = {
|
||||
timeStart,
|
||||
duration,
|
||||
delay: 0,
|
||||
dayOffset: 0 as Day,
|
||||
countToEnd: true,
|
||||
};
|
||||
|
||||
const expectedStart = getExpectedStart(testEvent, { ...baseState, offset: 0 });
|
||||
expect(getExpectedEnd(testEvent, expectedStart, baseState.currentDay)).toBe(timeStart + duration);
|
||||
});
|
||||
|
||||
test('a countToEnd event ignores upstream delays and stays pinned to its fixed end', () => {
|
||||
const testEvent = {
|
||||
timeStart: 100,
|
||||
duration: 50,
|
||||
delay: 20,
|
||||
dayOffset: 0 as Day,
|
||||
};
|
||||
|
||||
// events shift their schedule based on delay...
|
||||
const expectedStart = getExpectedStart({ ...testEvent }, { ...baseState, offset: 0 });
|
||||
expect(getExpectedEnd({ ...testEvent, countToEnd: false }, expectedStart, baseState.currentDay)).toBe(170);
|
||||
|
||||
// ... but count to end events stay pinned to the scheduled end
|
||||
const expectedStartCountToEnd = getExpectedStart({ ...testEvent }, { ...baseState, offset: 0 });
|
||||
expect(getExpectedEnd({ ...testEvent, countToEnd: true }, expectedStartCountToEnd, baseState.currentDay)).toBe(150);
|
||||
});
|
||||
|
||||
test('a countToEnd event drifts when a delay pushes its start past the fixed end', () => {
|
||||
const testEvent = {
|
||||
timeStart: 100,
|
||||
duration: 50,
|
||||
delay: 60,
|
||||
dayOffset: 0 as Day,
|
||||
countToEnd: true,
|
||||
};
|
||||
|
||||
// the delayed start (160) is past the fixed end (150), so the event can no longer
|
||||
// finish on time and the end follows the compromised start
|
||||
const expectedStart = getExpectedStart(testEvent, { ...baseState, offset: 0 });
|
||||
expect(getExpectedEnd(testEvent, expectedStart, baseState.currentDay)).toBe(160);
|
||||
});
|
||||
|
||||
test('a countToEnd event on a later day keeps the day offset on the end', () => {
|
||||
const testEvent = {
|
||||
timeStart: 100,
|
||||
duration: 50,
|
||||
delay: 0,
|
||||
dayOffset: 1 as Day,
|
||||
countToEnd: true,
|
||||
};
|
||||
|
||||
// the scheduled end must include the day offset (timeStart + dayInMs + duration),
|
||||
// not collapse to the day-shifted start
|
||||
const expectedStart = getExpectedStart(testEvent, { ...baseState, currentDay: 0, offset: 0 });
|
||||
expect(getExpectedEnd(testEvent, expectedStart, baseState.currentDay)).toBe(150 + dayInMs);
|
||||
|
||||
// when the running event is already on the same day, no extra day is added
|
||||
const expectedStartSameDay = getExpectedStart(
|
||||
{ ...testEvent, dayOffset: 0 as Day },
|
||||
{ ...baseState, currentDay: 0, offset: 0 },
|
||||
);
|
||||
expect(getExpectedEnd({ ...testEvent, dayOffset: 0 as Day }, expectedStartSameDay, baseState.currentDay)).toBe(150);
|
||||
});
|
||||
|
||||
test('a countToEnd event is anchored to its wall-clock end in relative mode', () => {
|
||||
const testEvent = {
|
||||
timeStart: 100,
|
||||
duration: 50,
|
||||
delay: 0,
|
||||
dayOffset: 0 as Day,
|
||||
countToEnd: true,
|
||||
};
|
||||
|
||||
const relativeState = {
|
||||
...baseState,
|
||||
mode: OffsetMode.Relative,
|
||||
actualStart: 30,
|
||||
plannedStart: 0,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
// a regular event in the same state is shifted by the relative-start offset to 180
|
||||
const expectedStartRegular = getExpectedStart({ ...testEvent }, relativeState);
|
||||
expect(getExpectedEnd({ ...testEvent, countToEnd: false }, expectedStartRegular, relativeState.currentDay)).toBe(
|
||||
180,
|
||||
);
|
||||
|
||||
// the countToEnd event stays pinned to its wall-clock end (150), not shifted
|
||||
const expectedStartCountToEnd = getExpectedStart(testEvent, relativeState);
|
||||
expect(getExpectedEnd(testEvent, expectedStartCountToEnd, relativeState.currentDay)).toBe(150);
|
||||
});
|
||||
});
|
||||
+22
-9
@@ -3,15 +3,6 @@ import { OffsetMode } from 'ontime-types';
|
||||
|
||||
import { dayInMs } from './conversionUtils.js';
|
||||
|
||||
/**
|
||||
* @param event the event that we are counting to
|
||||
* @param currentDay the day offset of the currently running event
|
||||
* @param totalGap accumulated gap from the current event
|
||||
* @param isLinkedToLoaded is this event part of a chain linking back to the current loaded event
|
||||
* @param clock
|
||||
* @param offset
|
||||
* @returns
|
||||
*/
|
||||
export function getExpectedStart(
|
||||
event: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'delay'>,
|
||||
state: {
|
||||
@@ -60,3 +51,25 @@ export function getExpectedStart(
|
||||
const offsetStartTimeBufferedByGaps = offsetStartTime - totalGap;
|
||||
return offsetStartTimeBufferedByGaps;
|
||||
}
|
||||
|
||||
export function getExpectedEnd(
|
||||
event: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'duration' | 'countToEnd'>,
|
||||
expectedStart: number,
|
||||
currentRuntimeDay: number,
|
||||
): number {
|
||||
/**
|
||||
* Count to end events are a special case
|
||||
* - the end time is always the wall clock
|
||||
*/
|
||||
if (event.countToEnd) {
|
||||
// account for day offset
|
||||
const relativeDayOffset = event.dayOffset - currentRuntimeDay;
|
||||
const plannedEnd = event.timeStart + event.duration + relativeDayOffset * dayInMs;
|
||||
|
||||
// count to end should finish on the planned time or on start
|
||||
return Math.max(expectedStart, plannedEnd);
|
||||
}
|
||||
|
||||
// for normal events, the expected end is when we would start + its duration
|
||||
return expectedStart + event.duration;
|
||||
}
|
||||
Reference in New Issue
Block a user