diff --git a/apps/client/src/common/utils/time.ts b/apps/client/src/common/utils/time.ts index 9990092ce..8e01b4496 100644 --- a/apps/client/src/common/utils/time.ts +++ b/apps/client/src/common/utils/time.ts @@ -3,6 +3,7 @@ import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND, + dayInMs, formatFromMillis, getExpectedStart, } from 'ontime-utils'; @@ -29,6 +30,11 @@ export function nowInMillis(): number { return elapsed; } +export function normaliseWallClock(time: number): number { + const timeOfDay = time % dayInMs; + return timeOfDay < 0 ? timeOfDay + dayInMs : timeOfDay; +} + /** * @description Resolves format from url and store * @return {string|null} A format string like "hh:mm:ss a" or null diff --git a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx index d682fac1c..76d0d6942 100644 --- a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx +++ b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTimes.tsx @@ -10,7 +10,7 @@ import Switch from '../../../../common/components/switch/Switch'; import Tooltip from '../../../../common/components/tooltip/Tooltip'; import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext'; import { millisToDelayString } from '../../../../common/utils/dateConfig'; -import { formatTime } from '../../../../common/utils/time'; +import { formatTime, normaliseWallClock } from '../../../../common/utils/time'; import TimeInputFlow from '../../time-input-flow/TimeInputFlow'; import style from '../EntryEditor.module.scss'; @@ -68,8 +68,10 @@ function EventEditorTimes({ }; const hasDelay = delay !== 0; + const delayedStart = normaliseWallClock(timeStart + delay); + const delayedEnd = normaliseWallClock(timeEnd + delay); const delayLabel = hasDelay - ? `Event is ${millisToDelayString(delay, 'expanded')}. New schedule ${formatTime(timeStart + delay)} → ${formatTime(timeEnd + delay)}` + ? `Event is ${millisToDelayString(delay, 'expanded')}. New schedule ${formatTime(delayedStart)} → ${formatTime(delayedEnd)}` : ''; return ( diff --git a/apps/client/src/features/rundown/rundown-event/__tests__/rundownEvent.utils.test.ts b/apps/client/src/features/rundown/rundown-event/__tests__/rundownEvent.utils.test.ts index d5d9d5671..8bc6e111d 100644 --- a/apps/client/src/features/rundown/rundown-event/__tests__/rundownEvent.utils.test.ts +++ b/apps/client/src/features/rundown/rundown-event/__tests__/rundownEvent.utils.test.ts @@ -1,10 +1,33 @@ +import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils'; + import { formatDelay } from '../rundownEvent.utils'; describe('formatDelay()', () => { it('adds a given delay to the start time', () => { - const timeStart = 60000; // 1 min - const delay = 60000; // 1 min + const timeStart = 1 * MILLIS_PER_MINUTE; // 00:01 + const delay = 1 * MILLIS_PER_MINUTE; // 00:01 const result = formatDelay(timeStart, delay); expect(result).toEqual('New start 00:02'); }); + + it('wraps negative delayed starts under midnight', () => { + const timeStart = 1 * MILLIS_PER_MINUTE; // 00:01 + const delay = -2 * MILLIS_PER_MINUTE; // -00:02 + const result = formatDelay(timeStart, delay); + expect(result).toEqual('New start 23:59'); + }); + + it('wraps later-day negative delays using delay as the source of truth', () => { + const timeStart = 1 * MILLIS_PER_HOUR; // 01:00 + const delay = -(1 * MILLIS_PER_HOUR + 30 * MILLIS_PER_MINUTE); // -01:30 + const result = formatDelay(timeStart, delay); + expect(result).toEqual('New start 23:30'); + }); + + it('displays positive delays as wall-clock time', () => { + const timeStart = 1 * MILLIS_PER_HOUR; // 01:00 + const delay = 1 * MILLIS_PER_HOUR + 30 * MILLIS_PER_MINUTE; // 01:30 + const result = formatDelay(timeStart, delay); + expect(result).toEqual('New start 02:30'); + }); }); diff --git a/apps/client/src/features/rundown/rundown-event/rundownEvent.utils.ts b/apps/client/src/features/rundown/rundown-event/rundownEvent.utils.ts index 443a74a4b..946a1ffb4 100644 --- a/apps/client/src/features/rundown/rundown-event/rundownEvent.utils.ts +++ b/apps/client/src/features/rundown/rundown-event/rundownEvent.utils.ts @@ -1,15 +1,16 @@ import { millisToString, removeTrailingZero } from 'ontime-utils'; -import { formatDuration } from '../../../common/utils/time'; +import { formatDuration, normaliseWallClock } from '../../../common/utils/time'; export function formatDelay(timeStart: number, delay: number): string | undefined { if (!delay) return; - const delayedStart = Math.max(0, timeStart + delay); + const delayedStart = normaliseWallClock(timeStart + delay); const timeTag = removeTrailingZero(millisToString(delayedStart)); return `New start ${timeTag}`; } + export function formatGap(gap: number, isNextDay: boolean) { if (gap === 0) { if (isNextDay) {