fix: delays account for midnight

This commit is contained in:
Carlos Valente
2026-07-14 13:06:35 +02:00
parent 0ff226f0ec
commit e1f6b22d19
4 changed files with 82 additions and 13 deletions
@@ -506,6 +506,7 @@ describe('getCurrent()', () => {
timeStart: 79200000, // 22:00:00
timeEnd: 600000, // 00:10:00
countToEnd: true,
dayOffset: 0,
},
clock: 79500000, // 22:05:00
timer: {
@@ -516,6 +517,7 @@ describe('getCurrent()', () => {
rundown: {
actualStart: 79200000,
plannedEnd: 600000,
currentDay: 0,
},
_timer: {
pausedAt: null,
@@ -527,6 +529,35 @@ describe('getCurrent()', () => {
expect(current).toBe(dayInMs - 79500000 + 600000);
});
it('handles overnight count-to-end after midnight', () => {
const state = {
eventNow: {
timeStart: 23 * MILLIS_PER_HOUR, // 23:00:00
timeEnd: 1 * MILLIS_PER_HOUR, // 01:00:00
countToEnd: true,
dayOffset: 0,
},
clock: 30 * MILLIS_PER_MINUTE, // 00:30:00 on day 1
timer: {
addedTime: 0,
duration: Infinity,
startedAt: 23 * MILLIS_PER_HOUR,
},
rundown: {
actualStart: 23 * MILLIS_PER_HOUR,
plannedEnd: 1 * MILLIS_PER_HOUR,
currentDay: 1,
},
_timer: {
pausedAt: null,
hasFinished: false,
},
} as RuntimeState;
const current = getCurrent(state);
expect(current).toBe(30 * MILLIS_PER_MINUTE);
});
it('handles events that were started late', () => {
const state = {
clock: 82000000, // 22:46:40 <--- starting 16 min after the scheduled end
+5 -3
View File
@@ -77,9 +77,11 @@ export function getCurrent(state: RuntimeState): number {
if (countToEnd) {
// count to end runs to its fixed end, so added time does not stretch the countdown
const isEventOverMidnight = timeStart > timeEnd;
const correctDay = isEventOverMidnight ? dayInMs : 0;
return correctDay - clock + timeEnd;
const dayOffset = state.eventNow.dayOffset ?? 0;
const currentDay =
state.rundown.currentDay ?? (timeStart > timeEnd && clock <= timeEnd ? dayOffset + 1 : dayOffset);
const endDay = timeStart > timeEnd ? dayOffset + 1 : dayOffset;
return timeEnd + endDay * dayInMs - (clock + currentDay * dayInMs);
}
if (startedAt === null) {
@@ -99,6 +99,36 @@ describe('getExpectedStart()', () => {
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: false })).toBe(110); // <-- when gap is not enough to compensate for the running behind it absorbs at much as possible
// expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(70); This should not be possible
});
test('negative delay cannot move a day 0 event before the rundown start', () => {
const testState = {
currentDay: 0,
totalGap: 0,
offset: 0,
mode: OffsetMode.Absolute,
actualStart: null,
plannedStart: null,
isLinkedToLoaded: true,
};
expect(getExpectedStart({ timeStart: 0, delay: -5, dayOffset: 0 as Day }, testState)).toBe(0);
expect(getExpectedStart({ timeStart: 10, delay: -5, dayOffset: 0 as Day }, testState)).toBe(5);
});
test('negative delay can move a later-day event back to the previous day', () => {
const testState = {
currentDay: 0,
totalGap: 0,
offset: 0,
mode: OffsetMode.Absolute,
actualStart: null,
plannedStart: null,
isLinkedToLoaded: true,
};
expect(getExpectedStart({ timeStart: 0, delay: -5, dayOffset: 1 as Day }, testState)).toBe(dayInMs - 5);
expect(getExpectedStart({ timeStart: 10, delay: -20, dayOffset: 1 as Day }, testState)).toBe(dayInMs - 10);
});
});
describe('Relative offset mode', () => {
@@ -413,6 +443,19 @@ describe('getExpectedEnd()', () => {
expect(getExpectedEnd({ ...testEvent, dayOffset: 0 as Day }, { ...baseState, currentDay: 0, offset: 0 })).toBe(150);
});
test('a countToEnd event with negative delay can start on the previous day but keeps its fixed end', () => {
const testEvent = {
timeStart: 0,
duration: 50,
delay: -10,
dayOffset: 1 as Day,
countToEnd: true,
};
expect(getExpectedStart(testEvent, { ...baseState, currentDay: 0, offset: 0 })).toBe(dayInMs - 10);
expect(getExpectedEnd(testEvent, { ...baseState, currentDay: 0, offset: 0 })).toBe(dayInMs + 50);
});
test('a countToEnd event is anchored to its wall-clock end in relative mode', () => {
const testEvent = {
timeStart: 100,
+3 -10
View File
@@ -23,13 +23,8 @@ export function getExpectedStart(
const { timeStart, dayOffset, delay } = event;
const { currentDay, totalGap, isLinkedToLoaded, offset, mode, actualStart, plannedStart } = state;
//How many days from the currently running event to this one
const relativeDayOffset = dayOffset - currentDay;
const delayedStart = Math.max(0, timeStart + delay);
//The normalised start time of this event relative to the currently running event
const normalisedTimeStart = delayedStart + relativeDayOffset * dayInMs;
const absoluteDelayedStart = Math.max(0, dayOffset * dayInMs + timeStart + delay);
const normalisedTimeStart = absoluteDelayedStart - currentDay * dayInMs;
let relativeStartOffset = 0;
@@ -69,9 +64,7 @@ export function getExpectedEnd(
* - the end time is always the wall clock
*/
if (event.countToEnd) {
// account for day offset
const relativeDayOffset = event.dayOffset - state.currentDay;
const plannedEnd = event.timeStart + event.duration + relativeDayOffset * dayInMs;
const plannedEnd = event.dayOffset * dayInMs + event.timeStart + event.duration - state.currentDay * dayInMs;
// count to end should finish on the planned time or on start
return Math.max(expectedStart, plannedEnd);