fix: roll loads events that cross midnight

This commit is contained in:
Carlos Valente
2026-03-03 00:44:03 +01:00
committed by Carlos Valente
parent 358ad79ae4
commit 155cf48a17
4 changed files with 82 additions and 14 deletions
@@ -709,6 +709,44 @@ describe('loadRoll() test that roll behaviour multi day event edge cases', () =>
expect(state).toStrictEqual(expected);
});
it('should recognise a playing event where its schedule spans over midnight, even with previous events', async () => {
vi.useFakeTimers();
await initRundown(
makeRundown({
order: ['1', '2'],
entries: {
'1': {
...mockEvent,
id: '1',
timeStart: 10 * MILLIS_PER_HOUR,
timeEnd: 11 * MILLIS_PER_HOUR,
duration: 1 * MILLIS_PER_HOUR,
},
'2': {
...mockEvent,
id: '2',
timeStart: 11 * MILLIS_PER_HOUR,
timeEnd: 2 * MILLIS_PER_HOUR,
duration: 14 * MILLIS_PER_HOUR
},
},
}),
{},
);
vi.runAllTimers();
vi.useRealTimers();
const { rundown, metadata } = rundownCache.get();
const now = 1 * MILLIS_PER_HOUR;
const expected = {
event: rundown.entries['2'],
index: 1,
};
const state = loadRoll(rundown, metadata, now);
expect(state).toStrictEqual(expected);
});
it('if the start time is the day after end time, and both are later than now', async () => {
vi.useFakeTimers();
await initRundown(
+26 -9
View File
@@ -1,4 +1,4 @@
import { dayInMs } from 'ontime-utils';
import { checkIsNow, dayInMs } from 'ontime-utils';
import { MaybeNumber, PlayableEvent, Rundown } from 'ontime-types';
import { normaliseEndTime } from './timerUtils.js';
@@ -36,11 +36,6 @@ export function loadRoll(
continue;
}
// we check if event crosses midnight
if (event.timeStart > event.timeEnd) {
daySpan++;
}
const correctedDays = dayInMs * daySpan;
const correctedStart = event.timeStart + correctedDays;
const correctedEnd = event.timeEnd + correctedDays;
@@ -49,13 +44,18 @@ export function loadRoll(
* there are 3 possible states for an event
* 1. event is already finished
* 2. event is running
* 3. event is in the future
* 3. event is running (midnight edge case)
* 4. event is in the future
*/
// 1. event is already finished
// when does the event end (handle midnight)
const normalEnd = normaliseEndTime(correctedStart, correctedEnd);
if (normalEnd <= timeNow) {
// keep track of day progress
if (event.timeStart > event.timeEnd) {
daySpan++;
}
continue;
}
@@ -66,8 +66,25 @@ export function loadRoll(
return { event, index: getTimedIndexFromPlayableIndex(metadata, i) };
}
// 3. event will run in the future
// we set the isPending flag to indicate that the event is currently playing
// 3. check if an overnight event that comes later is currently playing
for (let j = i + 1; j < metadata.playableEventOrder.length; j++) {
const futureEventId = metadata.playableEventOrder[j];
if (!futureEventId) continue;
const futureEvent = rundown.entries[futureEventId] as PlayableEvent;
if (!futureEvent || futureEvent.duration === 0) continue;
const crossesMidnight = futureEvent.timeStart > futureEvent.timeEnd;
if (!crossesMidnight) continue;
// check if timeNow is inside this overnight event
const isRunning = checkIsNow(futureEvent.timeStart, futureEvent.timeEnd, timeNow);
if (isRunning) {
return { event: futureEvent, index: getTimedIndexFromPlayableIndex(metadata, j) };
}
}
// 4. event will run in the future
return { event, index: getTimedIndexFromPlayableIndex(metadata, i), isPending: true };
}
@@ -24,6 +24,18 @@ describe('checkIsNow()', () => {
});
test('should return true accounting for events that roll over midnight', () => {
expect(checkIsNow(22 * MILLIS_PER_HOUR, 8 * MILLIS_PER_HOUR, 23 * MILLIS_PER_HOUR)).toBe(true);
const timeStart = 22 * MILLIS_PER_HOUR;
const timeEnd = 8 * MILLIS_PER_HOUR;
const now = 23 * MILLIS_PER_HOUR;
expect(checkIsNow(timeStart, timeEnd, now)).toBe(true);
});
test('should return true accounting for events that roll over midnight (2)', () => {
const timeStart = 22 * MILLIS_PER_HOUR;
const timeEnd = 8 * MILLIS_PER_HOUR;
const now = 1 * MILLIS_PER_HOUR;
expect(checkIsNow(timeStart, timeEnd, now)).toBe(true);
});
});
+5 -4
View File
@@ -1,9 +1,10 @@
import { dayInMs } from './conversionUtils.js';
/**
* Utility function checks whether a given event should be playing now
*/
export function checkIsNow(timeStart: number, timeEnd: number, clock: number): boolean {
const normalisedEnd = timeEnd < timeStart ? timeEnd + dayInMs : timeEnd;
return timeStart <= clock && clock <= normalisedEnd;
if (timeEnd < timeStart) {
// overnight event: clock is either after start OR before end
return clock >= timeStart || clock <= timeEnd;
}
return timeStart <= clock && clock <= timeEnd;
}