diff --git a/apps/client/src/views/timeline/Timeline.tsx b/apps/client/src/views/timeline/Timeline.tsx
index 6cfeb8ab6..c191bcb4e 100644
--- a/apps/client/src/views/timeline/Timeline.tsx
+++ b/apps/client/src/views/timeline/Timeline.tsx
@@ -1,7 +1,7 @@
import { memo } from 'react';
import { useViewportSize } from '@mantine/hooks';
-import { isOntimeEvent, isPlayableEvent, MaybeNumber, OntimeRundown } from 'ontime-types';
-import { checkIsNextDay, dayInMs, getLastEvent, MILLIS_PER_HOUR } from 'ontime-utils';
+import { isOntimeEvent, isPlayableEvent, OntimeRundown } from 'ontime-types';
+import { dayInMs, getLastEvent, MILLIS_PER_HOUR } from 'ontime-utils';
import TimelineMarkers from './timeline-markers/TimelineMarkers';
import { getElementPosition, getEndHour, getStartHour } from './timeline.utils';
@@ -30,10 +30,8 @@ function Timeline(props: TimelineProps) {
const startHour = getStartHour(firstStart);
const endHour = getEndHour(firstStart + totalDuration + (lastEvent?.delay ?? 0));
- let previousEventStartTime: MaybeNumber = null;
// we use selectedEventId as a signifier on whether the timeline is live
let eventStatus: ProgressStatus = selectedEventId ? 'done' : 'future';
- let elapsedDays = 0;
return (
@@ -52,14 +50,7 @@ function Timeline(props: TimelineProps) {
eventStatus = 'live';
}
- // we only need to check for next day if we have a previous event
- if (
- previousEventStartTime !== null &&
- checkIsNextDay(previousEventStartTime, event.timeStart, event.duration)
- ) {
- elapsedDays++;
- }
- const normalisedStart = event.timeStart + elapsedDays * dayInMs;
+ const normalisedStart = event.timeStart + event.dayOffset * dayInMs;
const { left: elementLeftPosition, width: elementWidth } = getElementPosition(
startHour * MILLIS_PER_HOUR,
@@ -69,9 +60,6 @@ function Timeline(props: TimelineProps) {
screenWidth,
);
- // prepare values for next iteration
- previousEventStartTime = normalisedStart;
-
return (
= {
type: SupportedEvent.Event,
revision: 0,
delay: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
custom: {},
diff --git a/apps/server/src/services/rundown-service/__tests__/delayUtils.test.ts b/apps/server/src/services/rundown-service/__tests__/delayUtils.test.ts
index 4c06f79d0..c78ce5300 100644
--- a/apps/server/src/services/rundown-service/__tests__/delayUtils.test.ts
+++ b/apps/server/src/services/rundown-service/__tests__/delayUtils.test.ts
@@ -150,11 +150,11 @@ describe('apply()', () => {
makeOntimeDelay(100),
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
// gap 50
- makeOntimeEvent({ id: '2', timeStart: 150, timeEnd: 200, duration: 50 }),
+ makeOntimeEvent({ id: '2', timeStart: 150, timeEnd: 200, duration: 50, gap: 50 }),
+ // gap 0
+ makeOntimeEvent({ id: '3', timeStart: 200, timeEnd: 250, duration: 50, gap: 0 }),
// gap 50
- makeOntimeEvent({ id: '3', timeStart: 200, timeEnd: 250, duration: 50 }),
- // gap 50
- makeOntimeEvent({ id: '4', timeStart: 300, timeEnd: 350, duration: 50 }),
+ makeOntimeEvent({ id: '4', timeStart: 300, timeEnd: 350, duration: 50, gap: 50 }),
// linked
makeOntimeEvent({ id: '5', timeStart: 350, timeEnd: 400, duration: 50, linkStart: '4' }),
];
@@ -165,7 +165,7 @@ describe('apply()', () => {
// gap 50 (100 - 50)
{ id: '2', timeStart: 150 + 50, timeEnd: 200 + 50, duration: 50, revision: 2 },
// gap 50 (50 - 50)
- { id: '3', timeStart: 200 + 50, timeEnd: 250 + 50, duration: 50, revision: 2 },
+ { id: '3', timeStart: 200 + 50, timeEnd: 250 + 50, duration: 50, revision: 2, gap: 0 },
// gap (delay is 0)
{ id: '4', timeStart: 300, timeEnd: 350, duration: 50, revision: 1 },
// linked
@@ -178,6 +178,8 @@ describe('apply()', () => {
makeOntimeDelay(2 * MILLIS_PER_HOUR),
makeOntimeEvent({
id: '1',
+ gap: 0,
+ dayOffset: 0,
timeStart: 46800000, // 13:00:00
timeEnd: 50400000, // 14:00:00
duration: MILLIS_PER_HOUR,
@@ -185,6 +187,8 @@ describe('apply()', () => {
// gap 1h
makeOntimeEvent({
id: '2',
+ gap: 1 * MILLIS_PER_HOUR,
+ dayOffset: 0,
timeStart: 54000000, // 15:00:00
timeEnd: 57600000, // 16:00:00
duration: MILLIS_PER_HOUR,
diff --git a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts
index f7a7b58b3..3e9684587 100644
--- a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts
+++ b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts
@@ -577,6 +577,8 @@ describe('calculateRuntimeDelays', () => {
type: SupportedEvent.Event,
revision: 0,
delay: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
@@ -605,6 +607,8 @@ describe('calculateRuntimeDelays', () => {
type: SupportedEvent.Event,
revision: 0,
delay: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
@@ -633,6 +637,8 @@ describe('calculateRuntimeDelays', () => {
type: SupportedEvent.Event,
revision: 0,
delay: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
@@ -661,6 +667,8 @@ describe('calculateRuntimeDelays', () => {
type: SupportedEvent.Event,
revision: 0,
delay: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
@@ -701,6 +709,8 @@ describe('getDelayAt()', () => {
timeDanger: 60000,
id: '659e1',
delay: 0,
+ dayOffset: 0,
+ gap: 0,
cue: '1',
custom: {},
},
@@ -725,6 +735,8 @@ describe('getDelayAt()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
@@ -753,6 +765,8 @@ describe('getDelayAt()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
@@ -781,6 +795,8 @@ describe('getDelayAt()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
@@ -835,6 +851,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
@@ -863,6 +881,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
@@ -891,6 +911,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
@@ -919,6 +941,8 @@ describe('calculateRuntimeDelaysFrom()', () => {
colour: '',
type: SupportedEvent.Event,
revision: 0,
+ dayOffset: 0,
+ gap: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
diff --git a/apps/server/src/services/rundown-service/__tests__/rundownCacheUtils.test.ts b/apps/server/src/services/rundown-service/__tests__/rundownCacheUtils.test.ts
index b927c6c4b..5904e38fd 100644
--- a/apps/server/src/services/rundown-service/__tests__/rundownCacheUtils.test.ts
+++ b/apps/server/src/services/rundown-service/__tests__/rundownCacheUtils.test.ts
@@ -9,12 +9,14 @@ import {
} from 'ontime-types';
import {
addToCustomAssignment,
+ calculateDayOffset,
getLink,
handleCustomField,
handleLink,
hasChanges,
isDataStale,
} from '../rundownCacheUtils.js';
+import { MILLIS_PER_HOUR } from 'ontime-utils';
describe('getLink()', () => {
it('should return null if there is no link', () => {
@@ -247,3 +249,61 @@ describe('hasChanges()', () => {
expect(hasChanges(existing, newEvent)).toBe(false);
});
});
+
+describe('calculateDayOffset', () => {
+ it('returns 0 if there is no previous event', () => {
+ expect(calculateDayOffset({ timeStart: 0 })).toBe(0);
+ });
+
+ it('returns 0 if the previous event duration is 0', () => {
+ expect(calculateDayOffset({ timeStart: 0 }, { timeStart: 0, duration: 0 })).toBe(0);
+ });
+
+ it('returns 0 if event starts after previous', () => {
+ expect(calculateDayOffset({ timeStart: 11 }, { timeStart: 10, duration: 2 })).toBe(0);
+ });
+
+ it('returns 1 if event starts before previous', () => {
+ expect(calculateDayOffset({ timeStart: 9 }, { timeStart: 10, duration: 2 })).toBe(1);
+ });
+
+ it('returns 1 if event starts at the same time as one before', () => {
+ expect(calculateDayOffset({ timeStart: 10 }, { timeStart: 10, duration: 2 })).toBe(1);
+ });
+
+ it('should account for an event that crossed midnight and there is a overlap', () => {
+ expect(
+ calculateDayOffset(
+ { timeStart: MILLIS_PER_HOUR }, // starts at 01:00:00
+ { timeStart: 20 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR }, // ends at 02:00:00
+ ),
+ ).toBe(1);
+ });
+
+ it('should account for an event that crossed midnight and there is a gap', () => {
+ expect(
+ calculateDayOffset(
+ { timeStart: 2 * MILLIS_PER_HOUR }, // starts at 02:00:00
+ { timeStart: 23 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR }, // ends at 01:00:00
+ ),
+ ).toBe(1);
+ });
+
+ it('should account for an event that crossed midnight with no overlaps or gaps', () => {
+ expect(
+ calculateDayOffset(
+ { timeStart: 2 * MILLIS_PER_HOUR }, // starts at 02:00:00
+ { timeStart: 20 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR }, // ends at 02:00:00
+ ),
+ ).toBe(1);
+ });
+
+ it('should account for an event that finishes exactly at midnight', () => {
+ expect(
+ calculateDayOffset(
+ { timeStart: 2 * MILLIS_PER_HOUR }, // starts at 02:00:00
+ { timeStart: 23 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR }, // ends at 24:00:00
+ ),
+ ).toBe(1);
+ });
+});
diff --git a/apps/server/src/services/rundown-service/delayUtils.ts b/apps/server/src/services/rundown-service/delayUtils.ts
index a1abeb83a..f625263fd 100644
--- a/apps/server/src/services/rundown-service/delayUtils.ts
+++ b/apps/server/src/services/rundown-service/delayUtils.ts
@@ -1,5 +1,5 @@
import { OntimeRundown, isOntimeDelay, isOntimeBlock, isOntimeEvent, OntimeEvent } from 'ontime-types';
-import { getTimeFromPrevious, deleteAtIndex } from 'ontime-utils';
+import { deleteAtIndex } from 'ontime-utils';
/**
* Calculates all delays in a given rundown
@@ -133,21 +133,14 @@ export function apply(eventId: string, rundown: OntimeRundown): OntimeRundown {
// if the event is not linked, we try and maintain gaps
if (lastEntry !== null) {
- const timeFromPrevious: number = getTimeFromPrevious(
- currentEntry.timeStart,
- lastEntry.timeStart,
- lastEntry.timeEnd,
- lastEntry.duration,
- );
-
// when applying negative delays, we need to unlink the event
// if the previous event was fully consumed by the delay
if (currentEntry.linkStart && delayValue < 0 && lastEntry.timeStart + delayValue < 0) {
shouldUnlink = true;
}
- if (timeFromPrevious > 0) {
- delayValue = Math.max(delayValue - timeFromPrevious, 0);
+ if (currentEntry.gap > 0) {
+ delayValue = Math.max(delayValue - currentEntry.gap, 0);
}
if (delayValue === 0) {
diff --git a/apps/server/src/services/rundown-service/rundownCache.ts b/apps/server/src/services/rundown-service/rundownCache.ts
index 9ffd5c71d..954c507ce 100644
--- a/apps/server/src/services/rundown-service/rundownCache.ts
+++ b/apps/server/src/services/rundown-service/rundownCache.ts
@@ -24,7 +24,7 @@ import {
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import { createPatch } from '../../utils/parser.js';
import { apply } from './delayUtils.js';
-import { handleCustomField, handleLink, hasChanges, isDataStale } from './rundownCacheUtils.js';
+import { calculateDayOffset, handleCustomField, handleLink, hasChanges, isDataStale } from './rundownCacheUtils.js';
type EventID = string;
type NormalisedRundown = Record;
@@ -55,6 +55,7 @@ function setIsStale() {
let totalDelay = 0;
let totalDuration = 0;
+let totalDays = 0;
let firstStart: MaybeNumber = null;
let lastEnd: MaybeNumber = null;
@@ -107,6 +108,7 @@ export function generate(
firstStart = null;
lastEnd = null;
totalDuration = 0;
+ totalDays = 0;
totalDelay = 0;
let lastEntry: PlayableEvent | null = null;
@@ -117,6 +119,7 @@ export function generate(
if (isOntimeEvent(currentEntry)) {
currentEntry.delay = 0;
+ currentEntry.gap = 0;
// 1. handle links - mutates updatedEvent
handleLink(i, initialRundown, currentEntry, links);
@@ -124,6 +127,9 @@ export function generate(
// 2. handle custom fields - mutates updatedEvent
handleCustomField(customFields, customFieldChangelog, currentEntry, assignedCustomFields);
+ totalDays += calculateDayOffset(currentEntry, lastEntry);
+ currentEntry.dayOffset = totalDays;
+
// update rundown metadata, it only concerns playable events
if (isPlayableEvent(currentEntry)) {
// fist start is always the first event
@@ -131,36 +137,31 @@ export function generate(
firstStart = currentEntry.timeStart;
}
- const timeFromPrevious: number = getTimeFromPrevious(
- currentEntry.timeStart,
- lastEntry?.timeStart,
- lastEntry?.timeEnd,
- lastEntry?.duration,
- );
+ currentEntry.gap = getTimeFromPrevious(currentEntry, lastEntry);
- if (timeFromPrevious === 0) {
+ if (currentEntry.gap === 0) {
// event starts on previous finish, we add its duration
totalDuration += currentEntry.duration;
- } else if (timeFromPrevious > 0) {
+ } else if (currentEntry.gap > 0) {
// event has a gap, we add the gap and the duration
- totalDuration += timeFromPrevious + currentEntry.duration;
- } else if (timeFromPrevious < 0) {
+ totalDuration += currentEntry.gap + currentEntry.duration;
+ } else if (currentEntry.gap < 0) {
// there is an overlap, we remove the overlap from the duration
// ensuring that the sum is not negative (ie: fully overlapped events)
// NOTE: we add the gap since it is a negative number
- totalDuration += Math.max(currentEntry.duration + timeFromPrevious, 0);
+ totalDuration += Math.max(currentEntry.duration + currentEntry.gap, 0);
}
// remove eventual gaps from the accumulated delay
// we only affect positive delays (time forwards)
- if (totalDelay > 0 && timeFromPrevious > 0) {
- totalDelay = Math.max(totalDelay - timeFromPrevious, 0);
+ if (totalDelay > 0 && currentEntry.gap > 0) {
+ totalDelay = Math.max(totalDelay - currentEntry.gap, 0);
}
// current event delay is the current accumulated delay
currentEntry.delay = totalDelay;
// lastEntry is the event with the latest end time
- if (isNewLatest(currentEntry.timeStart, currentEntry.timeEnd, lastEntry?.timeStart, lastEntry?.timeEnd)) {
+ if (isNewLatest(currentEntry, lastEntry)) {
lastEntry = currentEntry;
}
}
diff --git a/apps/server/src/services/rundown-service/rundownCacheUtils.ts b/apps/server/src/services/rundown-service/rundownCacheUtils.ts
index 31d66a2d8..8f165fc81 100644
--- a/apps/server/src/services/rundown-service/rundownCacheUtils.ts
+++ b/apps/server/src/services/rundown-service/rundownCacheUtils.ts
@@ -7,7 +7,7 @@ import {
OntimeRundownEntry,
OntimeBaseEvent,
} from 'ontime-types';
-import { getLinkedTimes } from 'ontime-utils';
+import { dayInMs, getLinkedTimes } from 'ontime-utils';
/**
* Get linked event
@@ -147,3 +147,36 @@ export function hasChanges(existingEvent: T, newEvent
(key) => !Object.hasOwn(existingEvent, key) || existingEvent[key as keyof T] !== newEvent[key as keyof T],
);
}
+
+/**
+ * Utility for calculating if the current events should have a day offset
+ * @param current the current event under test
+ * @param previous the previous event
+ * @returns 0 or 1 for easy accumulation with the total days
+ */
+export function calculateDayOffset(
+ current: Pick,
+ previous?: Pick,
+) {
+ // if there is no previous there can't be a day offset
+ if (!previous) {
+ return 0;
+ }
+
+ // if the previous events duration is zero it will push the current event to next day
+ if (previous.duration === 0) {
+ return 0;
+ }
+
+ // if the previous event crossed midnight then the current event is in the next day
+ if (previous.timeStart + previous.duration >= dayInMs) {
+ return 1;
+ }
+
+ // if the current events starts at the same time or before the previous event then it is the next day
+ if (current.timeStart <= previous.timeStart) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts
index 5ccd6cffd..4f7861ecf 100644
--- a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts
+++ b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts
@@ -37,6 +37,8 @@ describe('cellRequestFromEvent()', () => {
skip: false,
colour: 'red',
delay: 0,
+ gap: 0,
+ dayOffset: 0,
revision: 0,
id: '1358',
timeWarning: 0,
@@ -84,6 +86,8 @@ describe('cellRequestFromEvent()', () => {
colour: 'red',
revision: 0,
delay: 0,
+ gap: 0,
+ dayOffset: 0,
id: '1358',
timeWarning: 0,
timeDanger: 0,
@@ -132,6 +136,8 @@ describe('cellRequestFromEvent()', () => {
colour: 'red',
revision: 0,
delay: 0,
+ gap: 0,
+ dayOffset: 0,
id: '1358',
timeWarning: 0,
timeDanger: 0,
@@ -178,6 +184,8 @@ describe('cellRequestFromEvent()', () => {
skip: false,
colour: 'red',
delay: 0,
+ gap: 0,
+ dayOffset: 0,
revision: 0,
id: '1358',
timeWarning: 0,
@@ -212,6 +220,8 @@ describe('cellRequestFromEvent()', () => {
colour: 'red',
revision: 0,
delay: 0,
+ gap: 0,
+ dayOffset: 0,
id: '1358',
timeWarning: 0,
timeDanger: 0,
@@ -246,6 +256,8 @@ describe('cellRequestFromEvent()', () => {
colour: 'red',
revision: 0,
delay: 0,
+ gap: 0,
+ dayOffset: 0,
id: '1358',
timeWarning: 0,
timeDanger: 0,
diff --git a/apps/server/src/utils/parser.ts b/apps/server/src/utils/parser.ts
index f76c1f73f..68b14ca40 100644
--- a/apps/server/src/utils/parser.ts
+++ b/apps/server/src/utils/parser.ts
@@ -384,7 +384,9 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial {
+ await page.goto('http://localhost:4001/editor');
+
+ await page.getByRole('button', { name: 'Clear rundown' }).click();
+ await page.getByRole('button', { name: 'Delete all' }).click();
+
+ await page.getByRole('button', { name: 'Create Event' }).click();
+ await page.getByRole('button', { name: 'Event' }).nth(4).click();
+ await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
+ await page.getByTestId('entry-2').getByTestId('lock__end').getByRole('img').click();
+ await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').click();
+ await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').fill('23h');
+ await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').press('Enter');
+ await page.getByTestId('entry-3').getByTestId('time-input-duration').click();
+ await page.getByTestId('entry-3').getByTestId('time-input-duration').fill('2h');
+ await page.getByTestId('entry-3').getByTestId('time-input-duration').press('Enter');
+
+ await expect(page.getByTestId('entry-3').getByTestId('event-warning')).toBeVisible();
+});
+
+test('show warning when event starts next day midnight', async ({ page }) => {
+ await page.goto('http://localhost:4001/editor');
+
+ await page.getByRole('button', { name: 'Clear rundown' }).click();
+ await page.getByRole('button', { name: 'Delete all' }).click();
+
+ await page.getByRole('button', { name: 'Create Event' }).click();
+ await page.getByRole('button', { name: 'Event' }).nth(4).click();
+ await page.getByTestId('entry-2').getByText('E').click();
+ await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').click();
+ await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').fill('0');
+ await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').press('Enter');
+ await page.getByRole('button', { name: 'Event', exact: true }).nth(2).click();
+
+ await expect(page.getByText('(next day)')).toBeVisible();
+});
diff --git a/e2e/tests/fixtures/test-db.json b/e2e/tests/fixtures/test-db.json
index c84df4205..704802e74 100644
--- a/e2e/tests/fixtures/test-db.json
+++ b/e2e/tests/fixtures/test-db.json
@@ -16,7 +16,9 @@
"skip": false,
"note": "SF1.01",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.01",
"revision": 0,
"timeWarning": 120000,
@@ -42,7 +44,9 @@
"skip": false,
"note": "SF1.02",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.02",
"revision": 0,
"timeWarning": 120000,
@@ -68,7 +72,9 @@
"skip": false,
"note": "SF1.03",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.03",
"revision": 0,
"timeWarning": 120000,
@@ -94,7 +100,9 @@
"skip": false,
"note": "SF1.04",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.04",
"revision": 0,
"timeWarning": 120000,
@@ -120,7 +128,9 @@
"skip": false,
"note": "SF1.05",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.05",
"revision": 0,
"timeWarning": 120000,
@@ -151,7 +161,9 @@
"skip": false,
"note": "SF1.06",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.06",
"revision": 0,
"timeWarning": 120000,
@@ -177,7 +189,9 @@
"skip": false,
"note": "SF1.07",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.07",
"revision": 0,
"timeWarning": 120000,
@@ -203,7 +217,9 @@
"skip": false,
"note": "SF1.08",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.08",
"revision": 0,
"timeWarning": 120000,
@@ -229,7 +245,9 @@
"skip": false,
"note": "SF1.09",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.09",
"revision": 0,
"timeWarning": 120000,
@@ -255,7 +273,9 @@
"skip": false,
"note": "SF1.10",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.10",
"revision": 0,
"timeWarning": 120000,
@@ -286,7 +306,9 @@
"skip": false,
"note": "SF1.11",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.11",
"revision": 0,
"timeWarning": 120000,
@@ -312,7 +334,9 @@
"skip": false,
"note": "SF1.12",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.12",
"revision": 0,
"timeWarning": 120000,
@@ -338,7 +362,9 @@
"skip": false,
"note": "SF1.13",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.13",
"revision": 0,
"timeWarning": 120000,
@@ -364,7 +390,9 @@
"skip": false,
"note": "SF1.14",
"colour": "",
- "delay": 0,
+ "delay": 0,
+ "dayOffset": 0,
+ "gap": 0,
"cue": "SF1.14",
"revision": 0,
"timeWarning": 120000,
diff --git a/packages/types/src/definitions/core/OntimeEvent.type.ts b/packages/types/src/definitions/core/OntimeEvent.type.ts
index 02f1ce225..f2b2921a9 100644
--- a/packages/types/src/definitions/core/OntimeEvent.type.ts
+++ b/packages/types/src/definitions/core/OntimeEvent.type.ts
@@ -39,6 +39,8 @@ export type OntimeEvent = OntimeBaseEvent & {
colour: string;
revision: number;
delay: number; // calculated at runtime
+ dayOffset: number; // calculated at runtime
+ gap: number; // calculated at runtime
timeWarning: number;
timeDanger: number;
custom: EventCustomFields;
diff --git a/packages/utils/src/date-utils/checkIsNextDay.test.ts b/packages/utils/src/date-utils/checkIsNextDay.test.ts
index 8a0a7782a..9abbee852 100644
--- a/packages/utils/src/date-utils/checkIsNextDay.test.ts
+++ b/packages/utils/src/date-utils/checkIsNextDay.test.ts
@@ -2,66 +2,51 @@ 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 there is no previous event', () => {
+ const current = { timeStart: 0, dayOffset: 0 };
+ const previous = undefined;
+ expect(checkIsNextDay(current, previous)).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();
+ const current = { timeStart: 11, dayOffset: 0 };
+ const previous = { timeStart: 10, duration: 2, dayOffset: 0 };
+ expect(checkIsNextDay(current, previous)).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();
+ const current = { timeStart: 9, dayOffset: 1 };
+ const previous = { timeStart: 10, duration: 2, dayOffset: 0 };
+ expect(checkIsNextDay(current, previous)).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();
+ const current = { timeStart: 10, dayOffset: 1 };
+ const previous = { timeStart: 10, duration: 2, dayOffset: 0 };
+ expect(checkIsNextDay(current, previous)).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();
+ const current = { timeStart: 1 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 20 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR, dayOffset: 0 }; // event finished at 02:00:00
+ expect(checkIsNextDay(current, previous)).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();
+ const current = { timeStart: 2 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 23 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 0 }; // event finished at 01:00:00
+ expect(checkIsNextDay(current, previous)).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();
+ const current = { timeStart: 19 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 20 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR, dayOffset: 0 }; // event finished at 02:00:00
+ expect(checkIsNextDay(current, previous)).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();
- });
-
- it('should account for normalised start over multiple days', () => {
- const previousStart = 90000000; // 25:00:00
- const previousDuration = 1 * MILLIS_PER_HOUR;
- const timeStart = 0;
- expect(checkIsNextDay(previousStart, timeStart, previousDuration)).toBeTruthy();
+ const current = { timeStart: 2 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 23 * MILLIS_PER_HOUR, duration: 1 * MILLIS_PER_HOUR, dayOffset: 0 }; // event finished at 24:00:00
+ expect(checkIsNextDay(current, previous)).toBeTruthy();
});
});
diff --git a/packages/utils/src/date-utils/checkIsNextDay.ts b/packages/utils/src/date-utils/checkIsNextDay.ts
index e6a9732f3..71a629ba6 100644
--- a/packages/utils/src/date-utils/checkIsNextDay.ts
+++ b/packages/utils/src/date-utils/checkIsNextDay.ts
@@ -1,41 +1,27 @@
+import type { OntimeEvent } from 'ontime-types';
+
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 {
- if (previousDuration === 0) {
+export function checkIsNextDay(
+ current: Pick,
+ previous?: Pick,
+): boolean {
+ if (!previous) {
return false;
}
- const cappedStart = previousStart % dayInMs;
- if (timeStart <= cappedStart) {
- const normalisedPreviousEnd = cappedStart + previousDuration;
- if (normalisedPreviousEnd === dayInMs) {
- return true;
- }
- // handle exception for an event that finishes exactly at midnight
- if (normalisedPreviousEnd > dayInMs) {
- return false;
- }
- return true;
+ // if the day offsets are the same it can't be the next day
+ if (current.dayOffset <= previous.dayOffset) {
+ return false;
}
- return false;
+ // if the previous event crossed midnight then the current is the same day
+ if (previous.timeStart + previous.duration > dayInMs) {
+ return false;
+ }
+
+ return true;
}
diff --git a/packages/utils/src/date-utils/getTimeFromPrevious.test.ts b/packages/utils/src/date-utils/getTimeFromPrevious.test.ts
index 4fc37443b..a8633a2e0 100644
--- a/packages/utils/src/date-utils/getTimeFromPrevious.test.ts
+++ b/packages/utils/src/date-utils/getTimeFromPrevious.test.ts
@@ -1,64 +1,58 @@
-import { dayInMs, MILLIS_PER_HOUR } from './conversionUtils';
+import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from './conversionUtils';
import { getTimeFromPrevious } from './getTimeFromPrevious';
describe('getTimeFromPrevious', () => {
it('returns the time elapsed (gap or overlap) from the previous', () => {
- const previousStart = 69600000; // 19:20
- const previousEnd = 71700000; // 19:55
- const previousDuration = 2100000; // 35 minutes
- const currentStart = 75600000; // 21:00
const expected = 75600000 - 71700000; // current start - previousEnd
-
- expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
+ expect(
+ getTimeFromPrevious(
+ { timeStart: 21 * MILLIS_PER_HOUR, dayOffset: 0 },
+ { timeStart: 19 * MILLIS_PER_HOUR + 20 * MILLIS_PER_MINUTE, duration: 35 * MILLIS_PER_MINUTE, dayOffset: 0 },
+ ),
+ ).toBe(expected);
});
it('accounts for partially overlapping events', () => {
- const previousStart = 10;
- const previousEnd = 12;
- const previousDuration = 2;
- const currentStart = 11;
- const expected = -(previousEnd - currentStart);
-
- expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
+ const expected = -1;
+ expect(getTimeFromPrevious({ timeStart: 11, dayOffset: 0 }, { timeStart: 10, duration: 2, dayOffset: 0 })).toBe(
+ expected,
+ );
});
it('accounts for events that are fully contained', () => {
- const previousStart = 8;
- const previousEnd = 16;
- const previousDuration = 8;
- const currentStart = 10;
- const expected = -(previousEnd - currentStart);
-
- expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
+ const expected = -6;
+ expect(getTimeFromPrevious({ timeStart: 10, dayOffset: 0 }, { timeStart: 8, duration: 8, dayOffset: 0 })).toBe(
+ expected,
+ );
});
it('fully overlapping events are the next day', () => {
- const previousStart = 10 * MILLIS_PER_HOUR;
- const previousEnd = 12 * MILLIS_PER_HOUR;
- const previousDuration = previousEnd - previousStart;
- const currentStart = 10 * MILLIS_PER_HOUR;
- const expected = dayInMs - previousDuration;
-
- expect(getTimeFromPrevious(currentStart, previousStart, previousEnd, previousDuration)).toBe(expected);
+ const expected = dayInMs - 2 * MILLIS_PER_HOUR;
+ expect(
+ getTimeFromPrevious(
+ { timeStart: 10 * MILLIS_PER_HOUR, dayOffset: 1 },
+ { timeStart: 10 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 0 },
+ ),
+ ).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);
+ expect(
+ getTimeFromPrevious(
+ { timeStart: 22 * MILLIS_PER_HOUR, dayOffset: 0 },
+ { timeStart: 20 * MILLIS_PER_HOUR, duration: 3 * MILLIS_PER_HOUR, dayOffset: 0 },
+ ),
+ ).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);
+ expect(
+ getTimeFromPrevious(
+ { timeStart: 1 * MILLIS_PER_HOUR, dayOffset: 1 },
+ { timeStart: 20 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR, dayOffset: 0 },
+ ),
+ ).toBe(expected);
});
});
diff --git a/packages/utils/src/date-utils/getTimeFromPrevious.ts b/packages/utils/src/date-utils/getTimeFromPrevious.ts
index 7cdd521dd..f9227c18a 100644
--- a/packages/utils/src/date-utils/getTimeFromPrevious.ts
+++ b/packages/utils/src/date-utils/getTimeFromPrevious.ts
@@ -1,45 +1,33 @@
-import { checkIsNextDay } from './checkIsNextDay.js';
+import type { OntimeEvent } from 'ontime-types';
+
import { dayInMs } from './conversionUtils.js';
/**
- * Utility returns the time elapsed (gap or overlap) from the previous
- * It uses deconstructed parameters to simplify implementation in UI
+ * Utility returns the gap from previous event
*/
export function getTimeFromPrevious(
- currentStart: number,
- previousStart?: number,
- previousEnd?: number,
- previousDuration?: number,
+ current: Pick,
+ previous?: Pick,
): number {
// there is no previous event
- if (previousStart === undefined || previousEnd === undefined || previousDuration === undefined) {
+ if (!previous) {
return 0;
}
+ const normalisedCurrentStart = current.timeStart + current.dayOffset * dayInMs;
+ const normalisedPreviousEnd = previous.timeStart + previous.duration + previous.dayOffset * dayInMs;
+
// event is linked to previous
- if (currentStart === previousEnd) {
+ if (normalisedCurrentStart === normalisedPreviousEnd) {
return 0;
}
- // event is the day after
- if (checkIsNextDay(previousStart, currentStart, previousDuration)) {
- // time from previous is difference between normalised start and previous end
- return currentStart + dayInMs - previousEnd;
- }
-
// event has a gap from previous
- if (currentStart > previousEnd) {
+ if (normalisedCurrentStart > normalisedPreviousEnd) {
// time from previous is difference between start and previous end
- return currentStart - previousEnd;
+ return normalisedCurrentStart - normalisedPreviousEnd;
}
// event overlaps with previous
- const overlap = previousEnd - currentStart;
- if (overlap > 0) {
- // time is a negative number indicating the amount of overlap
- return -overlap;
- }
-
- // we need to make sure we return a number, but there are no business cases for this
- return 0;
+ return normalisedCurrentStart - normalisedPreviousEnd;
}
diff --git a/packages/utils/src/date-utils/isNewLatest.test.ts b/packages/utils/src/date-utils/isNewLatest.test.ts
index c7809d6a6..3dbef16b3 100644
--- a/packages/utils/src/date-utils/isNewLatest.test.ts
+++ b/packages/utils/src/date-utils/isNewLatest.test.ts
@@ -1,48 +1,52 @@
-import { MILLIS_PER_HOUR } from './conversionUtils';
+import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from './conversionUtils';
import { isNewLatest } from './isNewLatest';
describe('isNewLatest', () => {
it('should be true if there is no previous', () => {
- expect(isNewLatest(0, 60000)).toBeTruthy();
+ const current = { timeStart: 0, duration: MILLIS_PER_HOUR, dayOffset: 0 };
+ const previous = undefined;
+ expect(isNewLatest(current, previous)).toBe(true);
+ });
+
+ it('should be false if current is contained in the previous', () => {
+ const current = {
+ timeStart: 21 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE,
+ duration: 10 * MILLIS_PER_MINUTE,
+ dayOffset: 0,
+ };
+ const previous = { timeStart: 21 * MILLIS_PER_HOUR, duration: MILLIS_PER_HOUR, dayOffset: 0 };
+
+ expect(isNewLatest(current, previous)).toBe(false);
});
it('should be true if it starts when the previous finishes', () => {
- const nowStart = 10 * MILLIS_PER_HOUR;
- const nowEnd = 11 * MILLIS_PER_HOUR;
- const previousStart = 9 * MILLIS_PER_HOUR;
- const previousEnd = 10 * MILLIS_PER_HOUR;
- expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
+ const current = { timeStart: 10 * MILLIS_PER_HOUR, duration: MILLIS_PER_HOUR, dayOffset: 0 };
+ const previous = { timeStart: 9 * MILLIS_PER_HOUR, duration: MILLIS_PER_HOUR, dayOffset: 0 };
+ expect(isNewLatest(current, previous)).toBe(true);
});
it('should be true if it starts the same day the previous finishes', () => {
- const nowStart = 22 * MILLIS_PER_HOUR;
- const nowEnd = 23 * MILLIS_PER_HOUR;
- const previousStart = 9 * MILLIS_PER_HOUR;
- const previousEnd = 20 * MILLIS_PER_HOUR;
- expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
+ const current = { timeStart: 22 * MILLIS_PER_HOUR, duration: MILLIS_PER_HOUR, dayOffset: 0 };
+ const previous = { timeStart: 9 * MILLIS_PER_HOUR, duration: MILLIS_PER_HOUR, dayOffset: 0 };
+ expect(isNewLatest(current, previous)).toBe(true);
});
it('should be true if it finishes after the previous, accounting for passing midnight', () => {
- const nowStart = 1 * MILLIS_PER_HOUR;
- const nowEnd = 3 * MILLIS_PER_HOUR;
- const previousStart = 23 * MILLIS_PER_HOUR;
- const previousEnd = 2 * MILLIS_PER_HOUR;
- expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
+ const current = { timeStart: 1 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 23 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 0 };
+
+ expect(isNewLatest(current, previous)).toBe(true);
});
it('should be true if it the next day', () => {
- const nowStart = 8 * MILLIS_PER_HOUR;
- const nowEnd = 10 * MILLIS_PER_HOUR;
- const previousStart = 9 * MILLIS_PER_HOUR;
- const previousEnd = 11 * MILLIS_PER_HOUR;
- expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
+ const current = { timeStart: 8 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 9 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 0 };
+ expect(isNewLatest(current, previous)).toBe(true);
});
it('should be true if it the next day (2)', () => {
- const nowStart = 9 * MILLIS_PER_HOUR;
- const nowEnd = 11 * MILLIS_PER_HOUR;
- const previousStart = 9 * MILLIS_PER_HOUR;
- const previousEnd = 11 * MILLIS_PER_HOUR;
- expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
+ const current = { timeStart: 9 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 1 };
+ const previous = { timeStart: 9 * MILLIS_PER_HOUR, duration: 11 * MILLIS_PER_HOUR, dayOffset: 0 };
+ expect(isNewLatest(current, previous)).toBe(true);
});
});
diff --git a/packages/utils/src/date-utils/isNewLatest.ts b/packages/utils/src/date-utils/isNewLatest.ts
index 3e86e1a9a..7bdcfa342 100644
--- a/packages/utils/src/date-utils/isNewLatest.ts
+++ b/packages/utils/src/date-utils/isNewLatest.ts
@@ -1,24 +1,21 @@
-import { checkIsNextDay } from './checkIsNextDay.js';
+import type { OntimeEvent } from 'ontime-types';
+
+import { dayInMs } from './conversionUtils.js';
/**
* Checks whether a new element is the latest in the list
*/
-export function isNewLatest(timeStart: number, timeEnd: number, previousStart?: number, previousEnd?: number): boolean {
+export function isNewLatest(
+ currentEvent: Pick,
+ previousEvent?: Pick,
+) {
// true if there is no previous
- if (previousStart === undefined || previousEnd === undefined) {
+ if (!previousEvent) {
return true;
}
- // true if it starts after the previous is finished
- if (timeStart >= previousEnd) {
- return true;
- }
+ const normalisedCurrentEnd = currentEvent.timeStart + currentEvent.duration + currentEvent.dayOffset * dayInMs;
+ const normalisedPreviousEnd = previousEvent.timeStart + previousEvent.duration + previousEvent.dayOffset * dayInMs;
- // true if it finishes later than previous
- if (timeEnd > previousEnd) {
- return true;
- }
-
- // true if it is the day after
- return checkIsNextDay(previousStart, timeStart, previousEnd - previousStart);
+ return normalisedCurrentEnd >= normalisedPreviousEnd;
}