Integration/human readable time (#405)

* refactor: remove unused code

* feat: add human readable values to message parsing

---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>
Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
Fabian Posenau
2023-06-05 20:48:53 +02:00
committed by GitHub
parent bfb483508d
commit 7cfe7c0d78
19 changed files with 302 additions and 271 deletions
@@ -1,6 +1,5 @@
import { memo } from 'react';
import { formatDisplay } from '../../utils/dateConfig';
import { formatDisplay } from 'ontime-utils';
import './TimerDisplay.scss';
@@ -1,100 +1,4 @@
import {
forgivingStringToMillis,
formatDisplay,
isTimeString,
millisToDelayString,
millisToMinutes,
millisToSeconds,
} from '../dateConfig';
describe('test string from formatDisplay function', () => {
it('test with null values', () => {
const t = { val: null, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with not numbers', () => {
const t = { val: 'test', result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: '01:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: '01:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 86400 (24 hours)', () => {
const t = { val: 86400000, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 86401 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: '00:00:01' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with -86401 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: '00:00:01' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
});
describe('test string from formatDisplay function with hidezero', () => {
it('test with null values', () => {
const t = { val: null, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: '01:00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: '01:00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 86400 (24 hours)', () => {
const t = { val: 86400000, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 86401 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: '00:01' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with -86401 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: '00:01' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
});
import { forgivingStringToMillis, millisToDelayString, millisToMinutes, millisToSeconds } from '../dateConfig';
describe('test millisToSeconds function', () => {
it('test with null values', () => {
@@ -170,31 +74,6 @@ describe('test millisToMinutes function', () => {
});
});
describe('test isTimeString() function', () => {
it('it validates time strings', () => {
const ts = ['2', '2:10', '2:10:22'];
for (const s of ts) {
expect(isTimeString(s)).toBe(true);
}
});
it('it fails overloaded times', () => {
const ts = ['70', '89:10', '26:10:22'];
for (const s of ts) {
expect(isTimeString(s)).toBe(false);
}
});
});
describe('test isTimeString() function handle different separators', () => {
const ts = ['2:10', '2,10', '2.10'];
for (const s of ts) {
it(`it handles ${s}`, () => {
expect(isTimeString(s)).toBe(true);
});
}
});
describe('test forgivingStringToMillis()', () => {
describe('function handles time with no separators', () => {
const testData = [
@@ -5,29 +5,6 @@ import { mth, mtm, mts } from './timeConstants';
export const timeFormat = 'HH:mm';
export const timeFormatSeconds = 'HH:mm:ss';
/**
* another go at simpler string formatting (counters)
* @description Converts seconds to string representing time
* @param {number | null} milliseconds - time in seconds
* @param {boolean} [hideZero] - whether to show hours in case its 00
* @returns {string} String representing absolute time 00:12:02
*/
export function formatDisplay(milliseconds: number | null, hideZero = false): string {
if (typeof milliseconds !== 'number') {
return hideZero ? '00:00' : '00:00:00';
}
// add an extra 0 if necessary
const format = (val: number) => `0${Math.floor(val)}`.slice(-2);
const s = Math.abs(millisToSeconds(milliseconds));
const hours = Math.floor((s / 3600) % 24);
const minutes = Math.floor((s % 3600) / 60);
if (hideZero && hours < 1) return [minutes, s % 60].map(format).join(':');
return [hours, minutes, s % 60].map(format).join(':');
}
/**
* @description Converts milliseconds to seconds
* @param {number | null} millis - time in seconds
@@ -49,26 +26,6 @@ export const millisToMinutes = (millis: number): number => {
return millis < 0 ? Math.ceil(millis / mtm) : Math.floor(millis / mtm);
};
/**
* @description Validates a time string
* @param {string} string - time string "23:00:12"
* @returns {boolean} string represents time
*/
export const isTimeString = (string: string): boolean => {
// ^ # Start of string
// (?: # Try to match...
// (?: # Try to match...
// ([01]?\d|2[0-3]): # HH:
// )? # (optionally).
// ([0-5]?\d): # MM: (required)
// )? # (entire group optional, so either HH:MM:, MM: or nothing)
// ([0-5]?\d) # SS (required)
// $ # End of string
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)$/;
return regex.test(string);
};
/**
* @description safe parse string to int
* @param {string} valueAsString
@@ -12,7 +12,7 @@ interface IntegrationModalProps {
onClose: () => void;
}
const oscDocsUrl = 'https://ontime.gitbook.io/v2/control-and-feedback/osc';
const oscDocsUrl = 'https://ontime.gitbook.io/v2/control-and-feedback/integrations';
export default function IntegrationModal(props: IntegrationModalProps) {
const { isOpen, onClose } = props;
@@ -5,13 +5,13 @@ import { FiTarget } from '@react-icons/all-files/fi/FiTarget';
import { IoContract } from '@react-icons/all-files/io5/IoContract';
import { IoExpand } from '@react-icons/all-files/io5/IoExpand';
import { IoMoon } from '@react-icons/all-files/io5/IoMoon';
import { formatDisplay } from 'ontime-utils';
import PropTypes from 'prop-types';
import { TableSettingsContext } from '../../common/context/TableSettingsContext';
import useFullscreen from '../../common/hooks/useFullscreen';
import { useTimer } from '../../common/hooks/useSocket';
import useEventData from '../../common/hooks-query/useEventData';
import { formatDisplay } from '../../common/utils/dateConfig';
import { formatTime } from '../../common/utils/time';
import { tooltipDelayFast } from '../../ontimeConfig';
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
import QRCode from 'react-qr-code';
import { AnimatePresence, motion } from 'framer-motion';
import { EventData, Message, OntimeEvent, ViewSettings } from 'ontime-types';
import { formatDisplay } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { TIME_FORMAT_OPTION } from '../../../common/components/edit-form-drawer/constants';
@@ -14,7 +15,6 @@ import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
import TitleCard from '../../../common/components/title-card/TitleCard';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatDisplay } from '../../../common/utils/dateConfig';
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
import { formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -1,7 +1,7 @@
import { TimerType } from 'ontime-types';
import { formatDisplay } from 'ontime-utils';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatDisplay } from '../../../common/utils/dateConfig';
import { formatTime } from '../../../common/utils/time';
const formatOptions = {
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent, ViewSettings } from 'ontime-types';
import { formatDisplay } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { TIME_FORMAT_OPTION } from '../../../common/components/edit-form-drawer/constants';
@@ -8,7 +9,6 @@ import EditFormDrawer from '../../../common/components/edit-form-drawer/EditForm
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatDisplay } from '../../../common/utils/dateConfig';
import getDelayTo from '../../../common/utils/getDelayTo';
import { formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { millisToString } from 'ontime-utils';
import { formatDisplay, millisToString } from 'ontime-utils';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
@@ -9,7 +9,6 @@ import EditFormDrawer from '../../../common/components/edit-form-drawer/EditForm
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import useFitText from '../../../common/hooks/useFitText';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { formatDisplay } from '../../../common/utils/dateConfig';
import { formatEventList, getEventsWithDelay, trimRundown } from '../../../common/utils/eventsManager';
import { formatTime } from '../../../common/utils/time';
-1
View File
@@ -302,7 +302,6 @@ export class TimerService {
this.pausedTime,
this.timer.clock,
);
this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
}
}
@@ -1,56 +1,4 @@
import { parseTemplate, parseTemplateNested } from './integrationUtils.js';
describe('parseTemplate()', () => {
it('correctly parses a given string', () => {
const mockState = { test: 'this' };
const testString = 'That should replace {{test}}';
const expected = `That should replace ${mockState.test}`;
const result = parseTemplate(testString, mockState);
expect(result).toStrictEqual(expected);
});
it('parses string with multiple variables', () => {
const mockState = { test1: 'that', test2: 'this' };
const testString = '{{test1}} should replace {{test2}}';
const expected = `${mockState.test1} should replace ${mockState.test2}`;
const result = parseTemplate(testString, mockState);
expect(result).toStrictEqual(expected);
});
it('correctly parses a string without templates', () => {
const testString = 'That should replace {test}';
const result = parseTemplate(testString, {});
expect(result).toStrictEqual(testString);
});
it('handles scenarios with missing variables', () => {
// by failing to provide a value, we give visibility to
// potential issues in the given string
const mockState = { test1: 'that', test2: 'this' };
const testString = '{{test1}} should replace {{test2}}, but not {{test3}}';
const expected = `${mockState.test1} should replace ${mockState.test2}, but not {{test3}}`;
const result = parseTemplate(testString, mockState);
expect(result).toStrictEqual(expected);
});
it('doesnt yet handle nested variables', () => {
const mockState = {
timer: {
time: '10',
},
enabled: 'is',
};
const testString = 'Timer {{enabled}} enabled with {{timer.time}}ms interval';
const expected = 'Timer is enabled with {{timer.time}}ms interval';
const result = parseTemplate(testString, mockState);
expect(result).toStrictEqual(expected);
});
});
import { parseTemplateNested } from './integrationUtils.js';
describe('parseTemplateNested()', () => {
it('parses string with a single-level variable name', () => {
@@ -94,3 +42,57 @@ describe('parseTemplateNested()', () => {
expect(result).toStrictEqual(expected);
});
});
describe('parseNestedTemplate() -> resolveAliasData()', () => {
it('resolves data through callback', () => {
const data = {
not: {
so: {
easy: '3',
},
},
};
const aliases = {
easy: { key: 'not.so.easy', cb: (value: string) => `testing-${value}` },
};
const easyParse = parseTemplateNested('{{human.easy}}', data, aliases);
expect(easyParse).toBe('testing-3');
});
it('handles a mixed operation', () => {
const data = {
not: {
so: {
easy: '3',
},
},
other: {
value: 42,
},
};
const aliases = {
easy: { key: 'not.so.easy', cb: (value: string) => `testing-${value}` },
};
const easyParse = parseTemplateNested('{{other.value}} to {{human.easy}}', data, aliases);
expect(easyParse).toBe('42 to testing-3');
});
it('returns given key when not found', () => {
const data = {
not: {
so: {
easy: '3',
},
},
other: {
value: 5,
},
};
const aliases = {
easy: { key: 'not.so.easy', cb: (value: string) => `testing-${value}` },
};
const easyParse = parseTemplateNested('{{other.value}} to {{human.easy}} {{human.not.found}}', data, aliases);
expect(easyParse).toBe('5 to testing-3 {{human.not.found}}');
});
});
@@ -1,34 +1,65 @@
// any value inside double curly braces {{val}}
import { formatDisplay } from 'ontime-utils';
const placeholderRegex = /{{(.*?)}}/g;
/**
* Parses a templated string
*/
export function parseTemplate(template: string, state: object): string {
let parsedTemplate = template;
let match;
while ((match = placeholderRegex.exec(template)) !== null) {
const variableName = match[1];
if (Object.hasOwn(state, variableName)) {
parsedTemplate = parsedTemplate.replace(match[0], state[variableName]);
function formatDisplayFromString(value: string, hideZero = false): string {
let valueInNumber = null;
if (value !== 'null') {
const parsedValue = Number(value);
if (!Number.isNaN(parsedValue)) {
valueInNumber = parsedValue;
}
}
return parsedTemplate;
return formatDisplay(valueInNumber, hideZero);
}
type AliasesDefinition = Record<string, { key: string; cb: (value: unknown) => string }>;
const quickAliases: AliasesDefinition = {
clock: { key: 'timer.clock', cb: (value: string) => formatDisplayFromString(value) },
duration: { key: 'timer.duration', cb: (value: string) => formatDisplayFromString(value, true) },
expectedEnd: {
key: 'timer.expectedFinish',
cb: (value: string) => formatDisplayFromString(value),
},
runningTimer: {
key: 'timer.current',
cb: (value: string) => formatDisplayFromString(value, true),
},
elapsedTime: {
key: 'timer.elapsed',
cb: (value: string) => formatDisplayFromString(value, true),
},
startedAt: { key: 'timer.startedAt', cb: (value: string) => formatDisplayFromString(value) },
};
/**
* Parses a templated string to values in a nested object
*/
export function parseTemplateNested(template: string, state: object): string {
export function parseTemplateNested(template: string, state: object, humanReadable = quickAliases): string {
let parsedTemplate = template;
let match;
while ((match = placeholderRegex.exec(template)) !== null) {
const matches = Array.from(parsedTemplate.matchAll(placeholderRegex));
for (const match of matches) {
const variableName = match[1];
const variableParts = variableName.split('.');
// iterate through variable parts, and look for the property in the state object
const value = variableParts.reduce((obj, key) => obj && obj[key], state);
if (value !== undefined) {
let value = undefined;
if (variableParts[0] === 'human') {
const lookupKey = variableParts[1];
if (lookupKey in humanReadable) {
const newTemplate = `{{${humanReadable[lookupKey].key}}}`;
const parsed = parseTemplateNested(newTemplate, state, humanReadable);
value = humanReadable[lookupKey].cb(parsed);
} else {
value = undefined;
}
} else {
// iterate through variable parts, and look for the property in the state object
value = variableParts.reduce((obj, key) => obj && obj[key], state);
}
if (typeof value !== 'undefined') {
parsedTemplate = parsedTemplate.replace(match[0], value);
}
}
@@ -1,3 +1,5 @@
import { isTimeString } from 'ontime-utils';
const mts = 1000; // millis to seconds
const mtm = 1000 * 60; // millis to minutes
const mth = 1000 * 60 * 60; // millis to hours
@@ -6,32 +8,13 @@ export const timeFormat = 'HH:mm';
export const timeFormatSeconds = 'HH:mm:ss';
export const DAY_TO_MS = 86400000;
/**
* @description Validates a time string
* @param {string} string - time string "23:00:12"
* @returns {boolean} string represents time
*/
export const isTimeString = (string) => {
// ^ # Start of string
// (?: # Try to match...
// (?: # Try to match...
// ([01]?\d|2[0-3]): # HH:
// )? # (optionally).
// ([0-5]?\d): # MM: (required)
// )? # (entire group optional, so either HH:MM:, MM: or nothing)
// ([0-5]?\d) # SS (required)
// $ # End of string
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)$/;
return regex.test(string);
};
/**
* @description Converts an excel date to milliseconds
* @argument {string} date - excel string date
* @returns {number} - time in milliseconds
*/
export const dateToMillis = (date) => {
export const dateToMillis = (date: Date): number => {
const h = date.getHours();
const m = date.getMinutes();
const s = date.getSeconds();
@@ -44,7 +27,7 @@ export const dateToMillis = (date) => {
* @param valueAsString
* @return {number}
*/
const parse = (valueAsString) => {
const parse = (valueAsString: string): number => {
const parsed = parseInt(valueAsString, 10);
if (isNaN(parsed)) {
return 0;
@@ -58,7 +41,7 @@ const parse = (valueAsString) => {
* @param {boolean} fillLeft - autofill left = hours / right = seconds
* @returns {number} - time string in millis
*/
export const forgivingStringToMillis = (value, fillLeft = true) => {
export const forgivingStringToMillis = (value: string, fillLeft = true): number => {
let millis = 0;
// split string at known separators : , .
@@ -106,13 +89,25 @@ export const forgivingStringToMillis = (value, fillLeft = true) => {
* @returns {number} - time in milliseconds
*/
export const parseExcelDate = (excelDate) => {
export const parseExcelDate = (excelDate: string): number => {
// attempt converting to date object
const date = new Date(excelDate);
if (date instanceof Date && !isNaN(date)) {
if (date instanceof Date && !isNaN(date.getTime())) {
return dateToMillis(date);
} else if (isTimeString(excelDate)) {
return forgivingStringToMillis(excelDate);
}
return 0;
};
/**
* @description Converts milliseconds to seconds -- Copied from client code
* @param {number | null} millis - time in seconds
* @returns {number} Amount in seconds
*/
export const millisToSeconds = (millis: number | null): number => {
if (millis === null) {
return 0;
}
return millis < 0 ? Math.ceil(millis / mts) : Math.floor(millis / mts);
};
+2
View File
@@ -1,3 +1,5 @@
export { formatDisplay } from './src/date-utils/formatDisplay.js';
export { formatFromMillis } from './src/date-utils/formatFromMillis.js';
export { isTimeString } from './src/date-utils/isTimeString.js';
export { millisToString } from './src/date-utils/millisToString.js';
export { generateId } from './src/generate-id/generateId.js';
@@ -0,0 +1,91 @@
import { formatDisplay } from './formatDisplay';
describe('test string from formatDisplay function', () => {
it('test with null values', () => {
const t = { val: null, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with not numbers', () => {
const t = { val: 'test', result: '00:00:00' };
// @ts-expect-error -- indulge me for the test
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: '01:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: '01:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 86400 (24 hours)', () => {
const t = { val: 86400000, result: '00:00:00' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with 86401 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: '00:00:01' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
it('test with -86401 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: '00:00:01' };
expect(formatDisplay(t.val, false)).toBe(t.result);
});
});
describe('test string from formatDisplay function with hidezero', () => {
it('test with null values', () => {
const t = { val: null, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: '01:00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: '01:00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 86400 (24 hours)', () => {
const t = { val: 86400000, result: '00:00' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with 86401 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: '00:01' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
it('test with -86401 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: '00:01' };
expect(formatDisplay(t.val, true)).toBe(t.result);
});
});
@@ -0,0 +1,31 @@
import { mts } from '../timeConstants.js';
/**
* another go at simpler string formatting (counters) -- Copied from client code
* @description Converts seconds to string representing time
* @param {number | null} milliseconds - time in seconds
* @param {boolean} [hideZero] - whether to show hours in case its 00
* @returns {string} String representing absolute time 00:12:02
*/
export function formatDisplay(milliseconds: number | null, hideZero = false): string {
if (typeof milliseconds !== 'number') {
return hideZero ? '00:00' : '00:00:00';
}
// add an extra 0 if necessary
const format = (val: number) => `0${Math.floor(val)}`.slice(-2);
const s = Math.abs(millisToSeconds(milliseconds));
const hours = Math.floor((s / 3600) % 24);
const minutes = Math.floor((s % 3600) / 60);
if (hideZero && hours < 1) return [minutes, s % 60].map(format).join(':');
return [hours, minutes, s % 60].map(format).join(':');
}
export const millisToSeconds = (millis: number | null): number => {
if (millis === null) {
return 0;
}
return millis < 0 ? Math.ceil(millis / mts) : Math.floor(millis / mts);
};
@@ -0,0 +1,26 @@
import { isTimeString } from './isTimeString';
describe('test isTimeString() function', () => {
it('it validates time strings', () => {
const ts = ['2', '2:10', '2:10:22'];
for (const s of ts) {
expect(isTimeString(s)).toBe(true);
}
});
it('it fails overloaded times', () => {
const ts = ['70', '89:10', '26:10:22'];
for (const s of ts) {
expect(isTimeString(s)).toBe(false);
}
});
});
describe('test isTimeString() function handle different separators', () => {
const ts = ['2:10', '2,10', '2.10'];
for (const s of ts) {
it(`it handles ${s}`, () => {
expect(isTimeString(s)).toBe(true);
});
}
});
@@ -0,0 +1,19 @@
/**
* @description Validates a time string
* @param {string} text - time string "23:00:12"
* @returns {boolean} string represents time
*/
export const isTimeString = (text: string): boolean => {
// ^ # Start of string
// (?: # Try to match...
// (?: # Try to match...
// ([01]?\d|2[0-3]): # HH:
// )? # (optionally).
// ([0-5]?\d): # MM: (required)
// )? # (entire group optional, so either HH:MM:, MM: or nothing)
// ([0-5]?\d) # SS (required)
// $ # End of string
const regex = /^(?:(?:([01]?\d|2[0-3])[:,.])?([0-5]?\d)[:,.])?([0-5]?\d)$/;
return regex.test(text);
};
+1
View File
@@ -0,0 +1 @@
export const mts = 1000; // millis to seconds