diff --git a/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSectionScheduled.tsx b/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSectionScheduled.tsx index 04d7243ecd..917519256b 100644 --- a/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSectionScheduled.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSectionScheduled.tsx @@ -6,6 +6,7 @@ import EndDateBeforeStartDateError from '~/concepts/pipelines/content/createRun/ import CatchUp from '~/concepts/pipelines/content/createRun/contentSections/CatchUp'; import MaxConcurrencyField from '~/concepts/pipelines/content/createRun/contentSections/MaxConcurrencyField'; import TriggerTypeField from '~/concepts/pipelines/content/createRun/contentSections/TriggerTypeField'; +import { convertToDate } from '~/utilities/time'; type RunTypeSectionScheduledProps = { data: RunTypeScheduledData; @@ -39,7 +40,7 @@ const RunTypeSectionScheduled: React.FC = ({ data, onChange={(end) => onChange({ ...data, end })} adjustNow={(now) => { if (data.start) { - const start = new Date(`${data.start.date} ${data.start.time}`); + const start = convertToDate(data.start); start.setDate(start.getDate() + 7); return start; } diff --git a/frontend/src/concepts/pipelines/content/createRun/submitUtils.ts b/frontend/src/concepts/pipelines/content/createRun/submitUtils.ts index 9108f605e1..0960ee0f32 100644 --- a/frontend/src/concepts/pipelines/content/createRun/submitUtils.ts +++ b/frontend/src/concepts/pipelines/content/createRun/submitUtils.ts @@ -21,7 +21,7 @@ import { getInputDefinitionParams, isFilledRunFormData, } from '~/concepts/pipelines/content/createRun/utils'; -import { convertPeriodicTimeToSeconds } from '~/utilities/time'; +import { convertPeriodicTimeToSeconds, convertToDate } from '~/utilities/time'; const createRun = async ( formData: SafeRunFormData, @@ -46,12 +46,11 @@ const createRun = async ( return createPipelineRun({}, data); }; -const convertDateDataToKFDateTime = (dateData?: RunDateTime): DateTimeKF | null => { +export const convertDateDataToKFDateTime = (dateData?: RunDateTime): DateTimeKF | null => { if (!dateData) { return null; } - - const date = new Date(`${dateData.date} ${dateData.time}`); + const date = convertToDate(dateData); return date.toISOString(); }; diff --git a/frontend/src/concepts/pipelines/content/createRun/utils.ts b/frontend/src/concepts/pipelines/content/createRun/utils.ts index bd2804da16..10f88ffabd 100644 --- a/frontend/src/concepts/pipelines/content/createRun/utils.ts +++ b/frontend/src/concepts/pipelines/content/createRun/utils.ts @@ -6,8 +6,8 @@ import { ScheduledType, } from '~/concepts/pipelines/content/createRun/types'; import { ParametersKF, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; - import { getCorePipelineSpec } from '~/concepts/pipelines/getCorePipelineSpec'; +import { convertToDate } from '~/utilities/time'; const runTypeSafeData = (runType: RunFormData['runType']): boolean => runType.type !== RunTypeOption.SCHEDULED || @@ -18,10 +18,8 @@ export const isStartBeforeEnd = (start?: RunDateTime, end?: RunDateTime): boolea if (!start || !end) { return true; } - - const startDate = new Date(`${start.date} ${start.time}`); - const endDate = new Date(`${end.date} ${end.time}`); - + const startDate = convertToDate(start); + const endDate = convertToDate(end); return endDate.getTime() - startDate.getTime() > 0; }; @@ -29,8 +27,7 @@ const isValidDate = (value?: RunDateTime): boolean => { if (!value) { return true; } - - const date = new Date(`${value.date} ${value.time}`); + const date = convertToDate(value); return date.toString() !== 'Invalid Date'; }; diff --git a/frontend/src/utilities/__tests__/time.spec.ts b/frontend/src/utilities/__tests__/time.spec.ts index 68878e9413..6051a4de55 100644 --- a/frontend/src/utilities/__tests__/time.spec.ts +++ b/frontend/src/utilities/__tests__/time.spec.ts @@ -1,3 +1,4 @@ +import { RunDateTime } from '~/concepts/pipelines/content/createRun/types'; import { convertPeriodicTimeToSeconds, convertSecondsToPeriodicTime, @@ -7,6 +8,8 @@ import { ensureTimeFormat, printSeconds, relativeTime, + convertToTwentyFourHourTime, + convertToDate, } from '~/utilities/time'; describe('relativeDuration', () => { @@ -51,6 +54,53 @@ describe('convertDateToTimeString', () => { }); }); +describe('convertToDate', () => { + it('should convert to local date', () => { + const value: RunDateTime = { + date: '2024-01-04', + time: '11:55 PM', + }; + expect(convertToDate(value)).toStrictEqual(new Date('2024-01-04T23:55:00.000')); + }); + + it('should convert to local date using convertToTwentyFourHourTime function', () => { + const value: RunDateTime = { + date: '2024-01-04', + time: '12:30 PM', + }; + expect(convertToDate(value)).toStrictEqual( + new Date(`${value.date}T${convertToTwentyFourHourTime(value.time)}:00.000`), + ); + }); +}); + +describe('convertToTwentyFourHourTime', () => { + it('should convert 12 hour time to 24 hour time', () => { + const time = '1:30 AM'; + expect(convertToTwentyFourHourTime(time)).toBe('01:30'); + }); + + it('should convert past 12', () => { + const time = '2:55 PM'; + expect(convertToTwentyFourHourTime(time)).toBe('14:55'); + }); + + it('should convert from 10 AM <= x < 12PM to 24 hour time', () => { + const time = '11:24 AM'; + expect(convertToTwentyFourHourTime(time)).toBe('11:24'); + }); + + it('should convert from 12:30 PM to 12:30', () => { + const time = '12:30 PM'; + expect(convertToTwentyFourHourTime(time)).toBe('12:30'); + }); + + it('should convert from 12:30 AM to 00:30', () => { + const time = '12:30 AM'; + expect(convertToTwentyFourHourTime(time)).toBe('00:30'); + }); +}); + describe('ensureTimeFormat', () => { it('should return time value', () => { expect(ensureTimeFormat('3:30 PM')).toBe('3:30 PM'); diff --git a/frontend/src/utilities/time.ts b/frontend/src/utilities/time.ts index bce69b52e8..3b09aea014 100644 --- a/frontend/src/utilities/time.ts +++ b/frontend/src/utilities/time.ts @@ -1,5 +1,6 @@ import { PeriodicOptions, + RunDateTime, periodicOptionAsSeconds, } from '~/concepts/pipelines/content/createRun/types'; @@ -42,6 +43,21 @@ export const convertDateToTimeString = (date?: Date): string | null => { return `${hoursIn12}:${leadZero(date.getMinutes())} ${hours >= 12 ? 'PM' : 'AM'}`; }; +export const convertToDate = ({ date, time }: RunDateTime): Date => + new Date(`${date}T${convertToTwentyFourHourTime(time)}:00.000`); + +/* Return HH:MM from HH:MM *M */ +export const convertToTwentyFourHourTime = (time: string): string => { + const timeArray = time.trim().split(' '); + const hourMinutesArray = timeArray[0].trim().split(':'); + const hour = Number(hourMinutesArray[0]); + const minutes = hourMinutesArray[1]; + if (timeArray[1] === 'AM') { + return `${hour === 12 ? 0 : hour}:${minutes}`.padStart(5, '0'); + } + return `${hour === 12 ? hour : hour + 12}:${minutes}`; +}; + /** The TimeField component can sometimes cause '6:00PM' instead of '6:00 PM' if the user edits directly */ export const ensureTimeFormat = (time: string): string | null => { if (/\s[AP]M/.test(time)) {