diff --git a/spiffworkflow-frontend/src/components/ActiveUsers.tsx b/spiffworkflow-frontend/src/components/ActiveUsers.tsx
index 96e49373..3875cdb4 100644
--- a/spiffworkflow-frontend/src/components/ActiveUsers.tsx
+++ b/spiffworkflow-frontend/src/components/ActiveUsers.tsx
@@ -2,7 +2,8 @@ import { useEffect, useState } from 'react';
import HttpService from '../services/HttpService';
import { User } from '../interfaces';
-import { refreshAtInterval, REFRESH_TIMEOUT_SECONDS } from '../helpers';
+import { refreshAtInterval } from '../helpers';
+import DateAndTimeService from '../services/DateAndTimeService';
async function sha256(message: string) {
// encode as UTF-8
@@ -48,7 +49,7 @@ export default function ActiveUsers() {
return refreshAtInterval(
15,
- REFRESH_TIMEOUT_SECONDS,
+ DateAndTimeService.REFRESH_TIMEOUT_SECONDS,
updateActiveUsers,
unregisterUser
);
diff --git a/spiffworkflow-frontend/src/components/MessageInstanceList.tsx b/spiffworkflow-frontend/src/components/MessageInstanceList.tsx
index 1588a411..26ebacce 100644
--- a/spiffworkflow-frontend/src/components/MessageInstanceList.tsx
+++ b/spiffworkflow-frontend/src/components/MessageInstanceList.tsx
@@ -7,13 +7,13 @@ import { Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import ProcessBreadcrumb from './ProcessBreadcrumb';
import {
- convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
} from '../helpers';
import HttpService from '../services/HttpService';
import { FormatProcessModelDisplayName } from './MiniComponents';
import { MessageInstance } from '../interfaces';
+import DateAndTimeService from '../services/DateAndTimeService';
type OwnProps = {
processInstanceId?: number;
@@ -128,7 +128,9 @@ export default function MessageInstanceList({ processInstanceId }: OwnProps) {
{row.status} |
- {convertSecondsToFormattedDateTime(row.created_at_in_seconds)}
+ {DateAndTimeService.convertSecondsToFormattedDateTime(
+ row.created_at_in_seconds
+ )}
|
);
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index c6e02a8b..11343b1d 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -36,22 +36,13 @@ import {
DATE_FORMAT_FOR_DISPLAY,
} from '../config';
import {
- convertDateAndTimeStringsToSeconds,
- convertDateObjectToFormattedHoursMinutes,
- convertSecondsToFormattedDateString,
- convertSecondsToFormattedDateTime,
- convertSecondsToFormattedTimeHoursMinutes,
getKeyByValue,
getLastMilestoneFromProcessInstance,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
- REFRESH_INTERVAL_SECONDS,
- REFRESH_TIMEOUT_SECONDS,
titleizeString,
truncateString,
- formatDurationForDisplay,
- formatDateTime,
} from '../helpers';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
@@ -86,6 +77,7 @@ import { Can } from '../contexts/Can';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
import UserService from '../services/UserService';
import Filters from './Filters';
+import DateAndTimeService from '../services/DateAndTimeService';
type OwnProps = {
filtersEnabled?: boolean;
@@ -443,13 +435,15 @@ export default function ProcessInstanceListTable({
const timeFunctionToCall =
dateParametersToAlwaysFilterBy[reportFilter.field_name][1];
if (reportFilter.field_value) {
- const dateString = convertSecondsToFormattedDateString(
- reportFilter.field_value as any
- );
+ const dateString =
+ DateAndTimeService.convertSecondsToFormattedDateString(
+ reportFilter.field_value as any
+ );
dateFunctionToCall(dateString);
- const timeString = convertSecondsToFormattedTimeHoursMinutes(
- reportFilter.field_value as any
- );
+ const timeString =
+ DateAndTimeService.convertSecondsToFormattedTimeHoursMinutes(
+ reportFilter.field_value as any
+ );
timeFunctionToCall(timeString);
}
}
@@ -566,8 +560,8 @@ export default function ProcessInstanceListTable({
checkFiltersAndRun();
if (autoReload) {
clearRefreshRef.current = refreshAtInterval(
- REFRESH_INTERVAL_SECONDS,
- REFRESH_TIMEOUT_SECONDS,
+ DateAndTimeService.REFRESH_INTERVAL_SECONDS,
+ DateAndTimeService.REFRESH_TIMEOUT_SECONDS,
checkFiltersAndRun
);
return clearRefreshRef.current;
@@ -627,19 +621,22 @@ export default function ProcessInstanceListTable({
// with the use of the setErrorMessageSafely function. we are not sure why the context not
// changing still causes things to rerender when we call its setter without our extra check.
const calculateStartAndEndSeconds = (validate: boolean = true) => {
- const startFromSeconds = convertDateAndTimeStringsToSeconds(
- startFromDate,
- startFromTime || '00:00:00'
- );
- const startToSeconds = convertDateAndTimeStringsToSeconds(
- startToDate,
- startToTime || '00:00:00'
- );
- const endFromSeconds = convertDateAndTimeStringsToSeconds(
- endFromDate,
- endFromTime || '00:00:00'
- );
- const endToSeconds = convertDateAndTimeStringsToSeconds(
+ const startFromSeconds =
+ DateAndTimeService.convertDateAndTimeStringsToSeconds(
+ startFromDate,
+ startFromTime || '00:00:00'
+ );
+ const startToSeconds =
+ DateAndTimeService.convertDateAndTimeStringsToSeconds(
+ startToDate,
+ startToTime || '00:00:00'
+ );
+ const endFromSeconds =
+ DateAndTimeService.convertDateAndTimeStringsToSeconds(
+ endFromDate,
+ endFromTime || '00:00:00'
+ );
+ const endToSeconds = DateAndTimeService.convertDateAndTimeStringsToSeconds(
endToDate,
endToTime || '00:00:00'
);
@@ -872,7 +869,9 @@ export default function ProcessInstanceListTable({
onChange={(dateChangeEvent: any) => {
if (!initialDate && !initialTime) {
onChangeTimeFunction(
- convertDateObjectToFormattedHoursMinutes(new Date())
+ DateAndTimeService.convertDateObjectToFormattedHoursMinutes(
+ new Date()
+ )
);
}
onChangeDateFunction(dateChangeEvent.srcElement.value);
@@ -1691,7 +1690,7 @@ export default function ProcessInstanceListTable({
};
const formatSecondsForDisplay = (_row: ProcessInstance, seconds: any) => {
- return convertSecondsToFormattedDateTime(seconds) || '-';
+ return DateAndTimeService.convertSecondsToFormattedDateTime(seconds) || '-';
};
const defaultFormatter = (_row: ProcessInstance, value: any) => {
return value;
@@ -1710,8 +1709,8 @@ export default function ProcessInstanceListTable({
last_milestone_bpmn_name: formatLastMilestone,
};
const displayTypeFormatters: Record = {
- date_time: formatDateTime,
- duration: formatDurationForDisplay,
+ date_time: DateAndTimeService.formatDateTime,
+ duration: DateAndTimeService.formatDurationForDisplay,
};
const columnAccessor = column.accessor as keyof ProcessInstance;
const formatter = column.display_type
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceLogList.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceLogList.tsx
index ac2d4a03..adb7df79 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceLogList.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceLogList.tsx
@@ -15,7 +15,6 @@ import { createSearchParams, Link, useSearchParams } from 'react-router-dom';
import PaginationForTable from './PaginationForTable';
import {
getPageInfoFromSearchParams,
- convertSecondsToFormattedDateTime,
selectKeysFromSearchParams,
} from '../helpers';
import HttpService from '../services/HttpService';
@@ -32,6 +31,7 @@ import {
childrenForErrorObject,
errorForDisplayFromProcessInstanceErrorDetail,
} from './ErrorDisplay';
+import DateAndTimeService from '../services/DateAndTimeService';
type OwnProps = {
variant: string; // 'all' or 'for-me'
@@ -306,7 +306,11 @@ export default function ProcessInstanceLogList({
}
let timestampComponent = (
- {convertSecondsToFormattedDateTime(logEntry.timestamp)} |
+
+ {DateAndTimeService.convertSecondsToFormattedDateTime(
+ logEntry.timestamp
+ )}
+ |
);
if (logEntry.spiff_task_guid && logEntry.event_type !== 'task_cancelled') {
timestampComponent = (
@@ -317,7 +321,9 @@ export default function ProcessInstanceLogList({
to={`${processInstanceShowPageBaseUrl}/${logEntry.process_instance_id}/${logEntry.spiff_task_guid}`}
title="View state when task was completed"
>
- {convertSecondsToFormattedDateTime(logEntry.timestamp)}
+ {DateAndTimeService.convertSecondsToFormattedDateTime(
+ logEntry.timestamp
+ )}
);
diff --git a/spiffworkflow-frontend/src/components/TableCellWithTimeAgoInWords.tsx b/spiffworkflow-frontend/src/components/TableCellWithTimeAgoInWords.tsx
index fb3ddf54..64486f0e 100644
--- a/spiffworkflow-frontend/src/components/TableCellWithTimeAgoInWords.tsx
+++ b/spiffworkflow-frontend/src/components/TableCellWithTimeAgoInWords.tsx
@@ -1,6 +1,6 @@
// @ts-ignore
import { TimeAgo } from '../helpers/timeago';
-import { convertSecondsToFormattedDateTime } from '../helpers';
+import DateAndTimeService from '../services/DateAndTimeService';
type OwnProps = {
timeInSeconds: number;
@@ -16,7 +16,10 @@ export default function TableCellWithTimeAgoInWords({
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
diff --git a/spiffworkflow-frontend/src/components/TaskListTable.tsx b/spiffworkflow-frontend/src/components/TaskListTable.tsx
index c31aece2..075853b6 100644
--- a/spiffworkflow-frontend/src/components/TaskListTable.tsx
+++ b/spiffworkflow-frontend/src/components/TaskListTable.tsx
@@ -6,18 +6,16 @@ import { TimeAgo } from '../helpers/timeago';
import UserService from '../services/UserService';
import PaginationForTable from './PaginationForTable';
import {
- convertSecondsToFormattedDateTime,
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
- REFRESH_INTERVAL_SECONDS,
- REFRESH_TIMEOUT_SECONDS,
} from '../helpers';
import HttpService from '../services/HttpService';
import { PaginationObject, ProcessInstanceTask, Task } from '../interfaces';
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
import CustomForm from './CustomForm';
import InstructionsForEndUser from './InstructionsForEndUser';
+import DateAndTimeService from '../services/DateAndTimeService';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
@@ -100,8 +98,8 @@ export default function TaskListTable({
getTasks();
if (autoReload) {
return refreshAtInterval(
- REFRESH_INTERVAL_SECONDS,
- REFRESH_TIMEOUT_SECONDS,
+ DateAndTimeService.REFRESH_INTERVAL_SECONDS,
+ DateAndTimeService.REFRESH_TIMEOUT_SECONDS,
getTasks
);
}
@@ -288,7 +286,7 @@ export default function TaskListTable({
if (showDateStarted) {
rowElements.push(
|
- {convertSecondsToFormattedDateTime(
+ {DateAndTimeService.convertSecondsToFormattedDateTime(
processInstanceTask.created_at_in_seconds
) || '-'}
|
diff --git a/spiffworkflow-frontend/src/helpers.test.tsx b/spiffworkflow-frontend/src/helpers.test.tsx
index 042c8dcc..08e08e28 100644
--- a/spiffworkflow-frontend/src/helpers.test.tsx
+++ b/spiffworkflow-frontend/src/helpers.test.tsx
@@ -1,10 +1,8 @@
import {
- convertSecondsToFormattedDateString,
isANumber,
slugifyString,
underscorizeString,
recursivelyChangeNullAndUndefined,
- formatDurationForDisplay,
} from './helpers';
test('it can slugify a string', () => {
@@ -19,11 +17,6 @@ test('it can underscorize a string', () => {
);
});
-test('it can keep the correct date when converting seconds to date', () => {
- const dateString = convertSecondsToFormattedDateString(1666325400);
- expect(dateString).toEqual('2022-10-21');
-});
-
test('it can validate numeric values', () => {
expect(isANumber('11')).toEqual(true);
expect(isANumber('hey')).toEqual(false);
@@ -102,13 +95,3 @@ test('it can replace null values in object with undefined', () => {
expect(result.contacts.awesome).toEqual(false);
expect(result.contacts.info).toEqual('');
});
-
-test('it can properly format a duration', () => {
- expect(formatDurationForDisplay(null, '0')).toEqual('0s');
- expect(formatDurationForDisplay(null, '60')).toEqual('1m');
- expect(formatDurationForDisplay(null, '65')).toEqual('1m 5s');
- expect(formatDurationForDisplay(null, 65)).toEqual('1m 5s');
- expect(formatDurationForDisplay(null, 86500)).toEqual('1d 1m 40s');
- expect(formatDurationForDisplay(null, 2629746)).toEqual('30d 10h 29m 6s');
- expect(formatDurationForDisplay(null, 31536765)).toEqual('365d 12m 45s');
-});
diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx
index fbcdcd2b..d70837cc 100644
--- a/spiffworkflow-frontend/src/helpers.tsx
+++ b/spiffworkflow-frontend/src/helpers.tsx
@@ -1,11 +1,5 @@
-import { Duration, format } from 'date-fns';
import { Buffer } from 'buffer';
-import {
- DATE_TIME_FORMAT,
- DATE_FORMAT,
- TIME_FORMAT_HOURS_MINUTES,
-} from './config';
import { ProcessInstance } from './interfaces';
export const DEFAULT_PER_PAGE = 50;
@@ -87,128 +81,6 @@ export const titleizeString = (string: any) => {
return capitalizeFirstLetter((string || '').replaceAll('_', ' '));
};
-export const convertDateToSeconds = (
- date: any,
- onChangeFunction: any = null
-) => {
- let dateInSeconds = date;
- if (date !== null) {
- let dateInMilliseconds = date;
- if (typeof date.getTime === 'function') {
- dateInMilliseconds = date.getTime();
- }
- dateInSeconds = Math.floor(dateInMilliseconds / 1000);
- }
-
- if (onChangeFunction) {
- onChangeFunction(dateInSeconds);
- } else {
- return dateInSeconds;
- }
-
- return null;
-};
-
-export const convertDateObjectToFormattedString = (dateObject: Date) => {
- if (dateObject) {
- return format(dateObject, DATE_FORMAT);
- }
- return null;
-};
-
-export const dateStringToYMDFormat = (dateString: string) => {
- if (dateString && dateString.match(/^\d{2}-\d{2}-\d{4}$/)) {
- if (DATE_FORMAT.startsWith('dd')) {
- const d = dateString.split('-');
- return `${d[2]}-${d[1]}-${d[0]}`;
- }
- if (DATE_FORMAT.startsWith('MM')) {
- const d = dateString.split('-');
- return `${d[2]}-${d[0]}-${d[1]}`;
- }
- }
- return dateString;
-};
-
-export const convertDateAndTimeStringsToDate = (
- dateString: string,
- timeString: string
-) => {
- if (dateString && timeString) {
- return new Date(`${dateStringToYMDFormat(dateString)}T${timeString}`);
- }
- return null;
-};
-
-export const convertDateAndTimeStringsToSeconds = (
- dateString: string,
- timeString: string
-) => {
- const dateObject = convertDateAndTimeStringsToDate(dateString, timeString);
- if (dateObject) {
- return convertDateToSeconds(dateObject);
- }
- return null;
-};
-
-export const convertStringToDate = (dateString: string) => {
- return convertDateAndTimeStringsToDate(dateString, '00:10:00');
-};
-
-export const ymdDateStringToConfiguredFormat = (dateString: string) => {
- const dateObject = convertStringToDate(dateString);
- if (dateObject) {
- return convertDateObjectToFormattedString(dateObject);
- }
- return null;
-};
-
-export const convertSecondsToDateObject = (seconds: number) => {
- if (seconds) {
- return new Date(seconds * 1000);
- }
- return null;
-};
-
-export const convertSecondsToFormattedDateTime = (seconds: number) => {
- const dateObject = convertSecondsToDateObject(seconds);
- if (dateObject) {
- return format(dateObject, DATE_TIME_FORMAT);
- }
- return null;
-};
-
-export const convertDateObjectToFormattedHoursMinutes = (dateObject: Date) => {
- if (dateObject) {
- return format(dateObject, TIME_FORMAT_HOURS_MINUTES);
- }
- return null;
-};
-
-export const convertSecondsToFormattedTimeHoursMinutes = (seconds: number) => {
- const dateObject = convertSecondsToDateObject(seconds);
- if (dateObject) {
- return convertDateObjectToFormattedHoursMinutes(dateObject);
- }
- return null;
-};
-
-export const convertSecondsToFormattedDateString = (seconds: number) => {
- const dateObject = convertSecondsToDateObject(seconds);
- if (dateObject) {
- return convertDateObjectToFormattedString(dateObject);
- }
- return null;
-};
-
-export const convertDateStringToSeconds = (dateString: string) => {
- const dateObject = convertStringToDate(dateString);
- if (dateObject) {
- return convertDateToSeconds(dateObject);
- }
- return null;
-};
-
export const objectIsEmpty = (obj: object) => {
return Object.keys(obj).length === 0;
};
@@ -368,14 +240,6 @@ export const decodeBase64 = (data: string) => {
return Buffer.from(data, 'base64').toString('ascii');
};
-const MINUTES_IN_HOUR = 60;
-const SECONDS_IN_MINUTE = 60;
-const SECONDS_IN_HOUR = MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
-const FOUR_HOURS_IN_SECONDS = SECONDS_IN_HOUR * 4;
-
-export const REFRESH_INTERVAL_SECONDS = 5;
-export const REFRESH_TIMEOUT_SECONDS = FOUR_HOURS_IN_SECONDS;
-
export const getLastMilestoneFromProcessInstance = (
processInstance: ProcessInstance,
value: any
@@ -402,61 +266,3 @@ export const getLastMilestoneFromProcessInstance = (
}
return [valueToUse, truncatedValue];
};
-
-// logic from https://stackoverflow.com/a/28510323/6090676
-export const secondsToDuration = (secNum: number) => {
- const days = Math.floor(secNum / 86400);
- const hours = Math.floor(secNum / 3600) % 24;
- const minutes = Math.floor(secNum / 60) % 60;
- const seconds = secNum % 60;
-
- const duration: Duration = {
- days,
- hours,
- minutes,
- seconds,
- };
- return duration;
-};
-
-export const formatDurationForDisplay = (_row: any, value: any) => {
- if (value === undefined) {
- return undefined;
- }
- const duration = secondsToDuration(parseInt(value, 10));
- const durationTimes = [];
- if (duration.seconds !== undefined && duration.seconds > 0) {
- durationTimes.unshift(`${duration.seconds}s`);
- }
- if (duration.minutes !== undefined && duration.minutes > 0) {
- durationTimes.unshift(`${duration.minutes}m`);
- }
- if (duration.hours !== undefined && duration.hours > 0) {
- durationTimes.unshift(`${duration.hours}h`);
- }
- if (duration.days !== undefined && duration.days > 0) {
- durationTimes.unshift(`${duration.days}d`);
- }
- if (durationTimes.length < 1) {
- durationTimes.push('0s');
- }
- return durationTimes.join(' ');
-};
-
-export const formatDateTime = (_row: any, value: any) => {
- if (value === undefined || value === null) {
- return value;
- }
- let dateInSeconds = value;
- if (!isANumber(value)) {
- const timeArgs = value.split('T');
- dateInSeconds = convertDateAndTimeStringsToSeconds(
- timeArgs[0],
- timeArgs[1]
- );
- }
- if (dateInSeconds) {
- return convertSecondsToFormattedDateTime(dateInSeconds);
- }
- return null;
-};
diff --git a/spiffworkflow-frontend/src/rjsf/carbon_theme/BaseInputTemplate/BaseInputTemplate.tsx b/spiffworkflow-frontend/src/rjsf/carbon_theme/BaseInputTemplate/BaseInputTemplate.tsx
index 8954aba1..ab6d425e 100644
--- a/spiffworkflow-frontend/src/rjsf/carbon_theme/BaseInputTemplate/BaseInputTemplate.tsx
+++ b/spiffworkflow-frontend/src/rjsf/carbon_theme/BaseInputTemplate/BaseInputTemplate.tsx
@@ -9,7 +9,7 @@ import {
import { useCallback } from 'react';
import { DATE_FORMAT_CARBON, DATE_FORMAT_FOR_DISPLAY } from '../../../config';
-import { ymdDateStringToConfiguredFormat } from '../../../helpers';
+import DateAndTimeService from '../../../services/DateAndTimeService';
import { getCommonAttributes } from '../../helpers';
/** The `BaseInputTemplate` is the template to use to render the basic `` component for the `core` theme.
@@ -87,7 +87,7 @@ export default function BaseInputTemplate<
dateValue = value;
} else {
try {
- dateValue = ymdDateStringToConfiguredFormat(value);
+ dateValue = DateAndTimeService.ymdDateStringToConfiguredFormat(value);
// let the date component and form validators handle bad dates and do not blow up
} catch (RangeError) {}
}
diff --git a/spiffworkflow-frontend/src/rjsf/carbon_theme/DateWidget/DateWidget.tsx b/spiffworkflow-frontend/src/rjsf/carbon_theme/DateWidget/DateWidget.tsx
index 7a821641..1fd7e9db 100644
--- a/spiffworkflow-frontend/src/rjsf/carbon_theme/DateWidget/DateWidget.tsx
+++ b/spiffworkflow-frontend/src/rjsf/carbon_theme/DateWidget/DateWidget.tsx
@@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import { getTemplate, WidgetProps } from '@rjsf/utils';
-import { dateStringToYMDFormat } from '../../../helpers';
+import DateAndTimeService from '../../../services/DateAndTimeService';
function DateWidget(props: WidgetProps) {
const { onChange, options, registry } = props;
@@ -12,7 +12,7 @@ function DateWidget(props: WidgetProps) {
const handleChange = useCallback(
(value: any) => {
// react json schema forces y-m-d format for dates
- const newValue = dateStringToYMDFormat(value);
+ const newValue = DateAndTimeService.dateStringToYMDFormat(value);
onChange(newValue || undefined);
},
[onChange]
diff --git a/spiffworkflow-frontend/src/rjsf/custom_widgets/DateRangePicker/DateRangePickerWidget.tsx b/spiffworkflow-frontend/src/rjsf/custom_widgets/DateRangePicker/DateRangePickerWidget.tsx
index 2aeefc7f..cc4d9e2a 100644
--- a/spiffworkflow-frontend/src/rjsf/custom_widgets/DateRangePicker/DateRangePickerWidget.tsx
+++ b/spiffworkflow-frontend/src/rjsf/custom_widgets/DateRangePicker/DateRangePickerWidget.tsx
@@ -5,12 +5,8 @@ import {
DATE_FORMAT_FOR_DISPLAY,
DATE_RANGE_DELIMITER,
} from '../../../config';
-import {
- convertDateObjectToFormattedString,
- convertStringToDate,
- dateStringToYMDFormat,
-} from '../../../helpers';
import { getCommonAttributes } from '../../helpers';
+import DateAndTimeService from '../../../services/DateAndTimeService';
interface widgetArgs {
id: string;
@@ -52,13 +48,18 @@ export default function DateRangePickerWidget({
const onChangeLocal = useCallback(
(dateRange: Date[]) => {
let dateRangeString;
- const startDate = convertDateObjectToFormattedString(dateRange[0]);
+ const startDate = DateAndTimeService.convertDateObjectToFormattedString(
+ dateRange[0]
+ );
if (startDate) {
- const startDateYMD = dateStringToYMDFormat(startDate);
- const endDate = convertDateObjectToFormattedString(dateRange[1]);
+ const startDateYMD =
+ DateAndTimeService.dateStringToYMDFormat(startDate);
+ const endDate = DateAndTimeService.convertDateObjectToFormattedString(
+ dateRange[1]
+ );
dateRangeString = startDateYMD;
if (endDate) {
- const endDateYMD = dateStringToYMDFormat(endDate);
+ const endDateYMD = DateAndTimeService.dateStringToYMDFormat(endDate);
dateRangeString = `${dateRangeString}${DATE_RANGE_DELIMITER}${endDateYMD}`;
}
}
@@ -73,11 +74,11 @@ export default function DateRangePickerWidget({
let startDate = null;
let endDate = null;
try {
- startDate = convertStringToDate(startDateString);
+ startDate = DateAndTimeService.convertStringToDate(startDateString);
// eslint-disable-next-line no-empty
} catch (RangeError) {}
try {
- endDate = convertStringToDate(endDateString);
+ endDate = DateAndTimeService.convertStringToDate(endDateString);
// eslint-disable-next-line no-empty
} catch (RangeError) {}
diff --git a/spiffworkflow-frontend/src/routes/MyTasks.tsx b/spiffworkflow-frontend/src/routes/MyTasks.tsx
index 5be068dc..6c4c925d 100644
--- a/spiffworkflow-frontend/src/routes/MyTasks.tsx
+++ b/spiffworkflow-frontend/src/routes/MyTasks.tsx
@@ -8,8 +8,6 @@ import {
getPageInfoFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
- REFRESH_INTERVAL_SECONDS,
- REFRESH_TIMEOUT_SECONDS,
} from '../helpers';
import HttpService from '../services/HttpService';
import {
@@ -19,6 +17,7 @@ import {
RecentProcessModel,
} from '../interfaces';
import ProcessInstanceRun from '../components/ProcessInstanceRun';
+import DateAndTimeService from '../services/DateAndTimeService';
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
@@ -47,8 +46,8 @@ export default function MyTasks() {
getTasks();
refreshAtInterval(
- REFRESH_INTERVAL_SECONDS,
- REFRESH_TIMEOUT_SECONDS,
+ DateAndTimeService.REFRESH_INTERVAL_SECONDS,
+ DateAndTimeService.REFRESH_TIMEOUT_SECONDS,
getTasks
);
}, [searchParams]);
diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx
index 1fc40d94..b6d55fd7 100644
--- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx
+++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx
@@ -45,7 +45,6 @@ import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import HttpService from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor';
import {
- convertSecondsToFormattedDateTime,
getLastMilestoneFromProcessInstance,
HUMAN_TASK_TYPES,
modifyProcessIdentifierForPathParam,
@@ -78,6 +77,7 @@ import {
errorForDisplayFromString,
} from '../components/ErrorDisplay';
import { Notification } from '../components/Notification';
+import DateAndTimeService from '../services/DateAndTimeService';
type OwnProps = {
variant: string;
@@ -380,7 +380,9 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
- {lastUpdatedTimeLabel}:
-
- {convertSecondsToFormattedDateTime(lastUpdatedTime || 0) || 'N/A'}
+ {DateAndTimeService.convertSecondsToFormattedDateTime(
+ lastUpdatedTime || 0
+ ) || 'N/A'}
);
@@ -442,7 +444,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
- Started:
-
- {convertSecondsToFormattedDateTime(
+ {DateAndTimeService.convertSecondsToFormattedDateTime(
processInstance.start_in_seconds || 0
)}
diff --git a/spiffworkflow-frontend/src/services/DateAndTimeService.test.tsx b/spiffworkflow-frontend/src/services/DateAndTimeService.test.tsx
new file mode 100644
index 00000000..ae6fb488
--- /dev/null
+++ b/spiffworkflow-frontend/src/services/DateAndTimeService.test.tsx
@@ -0,0 +1,26 @@
+import DateAndTimeService from './DateAndTimeService';
+
+test('it can keep the correct date when converting seconds to date', () => {
+ const dateString =
+ DateAndTimeService.convertSecondsToFormattedDateString(1666325400);
+ expect(dateString).toEqual('2022-10-21');
+});
+test('it can properly format a duration', () => {
+ expect(DateAndTimeService.formatDurationForDisplay(null, '0')).toEqual('0s');
+ expect(DateAndTimeService.formatDurationForDisplay(null, '60')).toEqual('1m');
+ expect(DateAndTimeService.formatDurationForDisplay(null, '65')).toEqual(
+ '1m 5s'
+ );
+ expect(DateAndTimeService.formatDurationForDisplay(null, 65)).toEqual(
+ '1m 5s'
+ );
+ expect(DateAndTimeService.formatDurationForDisplay(null, 86500)).toEqual(
+ '1d 1m 40s'
+ );
+ expect(DateAndTimeService.formatDurationForDisplay(null, 2629746)).toEqual(
+ '30d 10h 29m 6s'
+ );
+ expect(DateAndTimeService.formatDurationForDisplay(null, 31536765)).toEqual(
+ '365d 12m 45s'
+ );
+});
diff --git a/spiffworkflow-frontend/src/services/DateAndTimeService.tsx b/spiffworkflow-frontend/src/services/DateAndTimeService.tsx
new file mode 100644
index 00000000..69e98db6
--- /dev/null
+++ b/spiffworkflow-frontend/src/services/DateAndTimeService.tsx
@@ -0,0 +1,216 @@
+import { Duration, format } from 'date-fns';
+import {
+ DATE_TIME_FORMAT,
+ DATE_FORMAT,
+ TIME_FORMAT_HOURS_MINUTES,
+} from '../config';
+import { isANumber } from '../helpers';
+
+const MINUTES_IN_HOUR = 60;
+const SECONDS_IN_MINUTE = 60;
+const SECONDS_IN_HOUR = MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
+const FOUR_HOURS_IN_SECONDS = SECONDS_IN_HOUR * 4;
+
+const REFRESH_INTERVAL_SECONDS = 5;
+const REFRESH_TIMEOUT_SECONDS = FOUR_HOURS_IN_SECONDS;
+
+const convertDateToSeconds = (date: any, onChangeFunction: any = null) => {
+ let dateInSeconds = date;
+ if (date !== null) {
+ let dateInMilliseconds = date;
+ if (typeof date.getTime === 'function') {
+ dateInMilliseconds = date.getTime();
+ }
+ dateInSeconds = Math.floor(dateInMilliseconds / 1000);
+ }
+
+ if (onChangeFunction) {
+ onChangeFunction(dateInSeconds);
+ } else {
+ return dateInSeconds;
+ }
+
+ return null;
+};
+
+const convertDateObjectToFormattedString = (dateObject: Date) => {
+ if (dateObject) {
+ return format(dateObject, DATE_FORMAT);
+ }
+ return null;
+};
+
+const dateStringToYMDFormat = (dateString: string) => {
+ if (dateString && dateString.match(/^\d{2}-\d{2}-\d{4}$/)) {
+ if (DATE_FORMAT.startsWith('dd')) {
+ const d = dateString.split('-');
+ return `${d[2]}-${d[1]}-${d[0]}`;
+ }
+ if (DATE_FORMAT.startsWith('MM')) {
+ const d = dateString.split('-');
+ return `${d[2]}-${d[0]}-${d[1]}`;
+ }
+ }
+ return dateString;
+};
+
+const convertDateAndTimeStringsToDate = (
+ dateString: string,
+ timeString: string
+) => {
+ if (dateString && timeString) {
+ return new Date(`${dateStringToYMDFormat(dateString)}T${timeString}`);
+ }
+ return null;
+};
+
+const convertDateAndTimeStringsToSeconds = (
+ dateString: string,
+ timeString: string
+) => {
+ const dateObject = convertDateAndTimeStringsToDate(dateString, timeString);
+ if (dateObject) {
+ return convertDateToSeconds(dateObject);
+ }
+ return null;
+};
+
+const convertStringToDate = (dateString: string) => {
+ return convertDateAndTimeStringsToDate(dateString, '00:10:00');
+};
+
+const ymdDateStringToConfiguredFormat = (dateString: string) => {
+ const dateObject = convertStringToDate(dateString);
+ if (dateObject) {
+ return convertDateObjectToFormattedString(dateObject);
+ }
+ return null;
+};
+
+const convertSecondsToDateObject = (seconds: number) => {
+ if (seconds) {
+ return new Date(seconds * 1000);
+ }
+ return null;
+};
+
+const convertSecondsToFormattedDateTime = (seconds: number) => {
+ const dateObject = convertSecondsToDateObject(seconds);
+ if (dateObject) {
+ return format(dateObject, DATE_TIME_FORMAT);
+ }
+ return null;
+};
+
+const convertDateObjectToFormattedHoursMinutes = (dateObject: Date) => {
+ if (dateObject) {
+ return format(dateObject, TIME_FORMAT_HOURS_MINUTES);
+ }
+ return null;
+};
+
+const convertSecondsToFormattedTimeHoursMinutes = (seconds: number) => {
+ const dateObject = convertSecondsToDateObject(seconds);
+ if (dateObject) {
+ return convertDateObjectToFormattedHoursMinutes(dateObject);
+ }
+ return null;
+};
+
+const convertSecondsToFormattedDateString = (seconds: number) => {
+ const dateObject = convertSecondsToDateObject(seconds);
+ if (dateObject) {
+ return convertDateObjectToFormattedString(dateObject);
+ }
+ return null;
+};
+
+const convertDateStringToSeconds = (dateString: string) => {
+ const dateObject = convertStringToDate(dateString);
+ if (dateObject) {
+ return convertDateToSeconds(dateObject);
+ }
+ return null;
+};
+
+// logic from https://stackoverflow.com/a/28510323/6090676
+const secondsToDuration = (secNum: number) => {
+ const days = Math.floor(secNum / 86400);
+ const hours = Math.floor(secNum / 3600) % 24;
+ const minutes = Math.floor(secNum / 60) % 60;
+ const seconds = secNum % 60;
+
+ const duration: Duration = {
+ days,
+ hours,
+ minutes,
+ seconds,
+ };
+ return duration;
+};
+
+const formatDurationForDisplay = (_row: any, value: any) => {
+ if (value === undefined) {
+ return undefined;
+ }
+ const duration = secondsToDuration(parseInt(value, 10));
+ const durationTimes = [];
+ if (duration.seconds !== undefined && duration.seconds > 0) {
+ durationTimes.unshift(`${duration.seconds}s`);
+ }
+ if (duration.minutes !== undefined && duration.minutes > 0) {
+ durationTimes.unshift(`${duration.minutes}m`);
+ }
+ if (duration.hours !== undefined && duration.hours > 0) {
+ durationTimes.unshift(`${duration.hours}h`);
+ }
+ if (duration.days !== undefined && duration.days > 0) {
+ durationTimes.unshift(`${duration.days}d`);
+ }
+ if (durationTimes.length < 1) {
+ durationTimes.push('0s');
+ }
+ return durationTimes.join(' ');
+};
+
+const formatDateTime = (_row: any, value: any) => {
+ if (value === undefined || value === null) {
+ return value;
+ }
+ let dateInSeconds = value;
+ if (!isANumber(value)) {
+ const timeArgs = value.split('T');
+ dateInSeconds = convertDateAndTimeStringsToSeconds(
+ timeArgs[0],
+ timeArgs[1]
+ );
+ }
+ if (dateInSeconds) {
+ return convertSecondsToFormattedDateTime(dateInSeconds);
+ }
+ return null;
+};
+
+const DateAndTimeService = {
+ REFRESH_INTERVAL_SECONDS,
+ REFRESH_TIMEOUT_SECONDS,
+
+ convertDateAndTimeStringsToDate,
+ convertDateAndTimeStringsToSeconds,
+ convertDateObjectToFormattedHoursMinutes,
+ convertDateObjectToFormattedString,
+ convertDateStringToSeconds,
+ convertDateToSeconds,
+ convertSecondsToDateObject,
+ convertSecondsToFormattedDateString,
+ convertSecondsToFormattedDateTime,
+ convertSecondsToFormattedTimeHoursMinutes,
+ convertStringToDate,
+ dateStringToYMDFormat,
+ formatDateTime,
+ formatDurationForDisplay,
+ secondsToDuration,
+ ymdDateStringToConfiguredFormat,
+};
+
+export default DateAndTimeService;
diff --git a/spiffworkflow-frontend/src/services/FormattingService.tsx b/spiffworkflow-frontend/src/services/FormattingService.tsx
index 4ae7b735..07faa8e6 100644
--- a/spiffworkflow-frontend/src/services/FormattingService.tsx
+++ b/spiffworkflow-frontend/src/services/FormattingService.tsx
@@ -1,8 +1,9 @@
-import { formatDateTime, formatDurationForDisplay } from '../helpers';
+import DateAndTimeService from './DateAndTimeService';
const spiffFormatFunctions: { [key: string]: Function } = {
- convert_seconds_to_date_time_for_display: formatDateTime,
- convert_seconds_to_duration_for_display: formatDurationForDisplay,
+ convert_seconds_to_date_time_for_display: DateAndTimeService.formatDateTime,
+ convert_seconds_to_duration_for_display:
+ DateAndTimeService.formatDurationForDisplay,
};
const checkForSpiffFormats = (markdown: string) => {