mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-12-10 00:48:37 -06:00
Compare commits
6 Commits
7cec37b7fe
...
41c8d82859
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41c8d82859 | ||
|
|
d9917c6b3e | ||
|
|
55354e3cd7 | ||
|
|
e67612424d | ||
|
|
9d25c37f4b | ||
|
|
b1f63a3207 |
@ -113,5 +113,11 @@
|
|||||||
"zeroPaddingDescription": "Make all time components always be two digits wide.",
|
"zeroPaddingDescription": "Make all time components always be two digits wide.",
|
||||||
"zeroPrintDescription": "Display the dropped parts as zero values \"00\".",
|
"zeroPrintDescription": "Display the dropped parts as zero values \"00\".",
|
||||||
"zeroPrintTruncatedParts": "Zero-print Truncated Parts"
|
"zeroPrintTruncatedParts": "Zero-print Truncated Parts"
|
||||||
|
},
|
||||||
|
"convertTimeToDecimal": {
|
||||||
|
"title": "Convert time to decimal",
|
||||||
|
"description": "Convert a formatted time duration (HH:MM:SS) into a decimal hour value.",
|
||||||
|
"shortDescription": "Convert human time to decimal time",
|
||||||
|
"longDescription": "Convert a formatted time string (HH:MM:SS or HH:MM) into its decimal-hour equivalent. Hours can be any positive number, while minutes and seconds accept values from 0-59 and can be single or double digits (e.g., '12:5' or '12:05'). This function interprets hours, minutes, and seconds, then calculates the total duration as a single decimal value. It is useful for productivity tracking, payroll calculations, time-based billing, data analysis, or any workflow that requires converting human-readable time into a numerical format that can be easily summed, compared, or processed. For example, '03:26:00' becomes 3.43 hours, and '12:5' becomes 12.08 hours."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
|
import { convertTimeToDecimal } from './service';
|
||||||
|
|
||||||
|
describe('convert-time-to-decimal', () => {
|
||||||
|
it('should convert time to decimal with default decimal places', () => {
|
||||||
|
const input = '31:23:59';
|
||||||
|
const result = convertTimeToDecimal(input, { decimalPlaces: '6' });
|
||||||
|
expect(result).toBe('31.399722');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert time to decimal with specified decimal places', () => {
|
||||||
|
const input = '31:23:59';
|
||||||
|
const result = convertTimeToDecimal(input, { decimalPlaces: '10' });
|
||||||
|
expect(result).toBe('31.3997222222');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert time to decimal with supplied format of HH:MM:SS', () => {
|
||||||
|
const input = '13:25:30';
|
||||||
|
const result = convertTimeToDecimal(input, { decimalPlaces: '6' });
|
||||||
|
expect(result).toBe('13.425000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert time to decimal with supplied format of HH:MM', () => {
|
||||||
|
const input = '13:25';
|
||||||
|
const result = convertTimeToDecimal(input, { decimalPlaces: '6' });
|
||||||
|
expect(result).toBe('13.416667');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert time to decimal with supplied format of HH:MM:', () => {
|
||||||
|
const input = '13:25';
|
||||||
|
const result = convertTimeToDecimal(input, { decimalPlaces: '6' });
|
||||||
|
expect(result).toBe('13.416667');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert time to decimal with supplied format of HH.MM.SS', () => {
|
||||||
|
const input = '13.25.30';
|
||||||
|
const result = convertTimeToDecimal(input, { decimalPlaces: '6' });
|
||||||
|
expect(result).toBe('13.425000');
|
||||||
|
});
|
||||||
|
});
|
||||||
72
src/pages/tools/time/convert-time-to-decimal/index.tsx
Normal file
72
src/pages/tools/time/convert-time-to-decimal/index.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Box } from '@mui/material';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import ToolContent from '@components/ToolContent';
|
||||||
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
|
import ToolTextInput from '@components/input/ToolTextInput';
|
||||||
|
import ToolTextResult from '@components/result/ToolTextResult';
|
||||||
|
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||||
|
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||||
|
import { convertTimeToDecimal } from './service';
|
||||||
|
import { InitialValuesType } from './types';
|
||||||
|
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||||
|
|
||||||
|
const initialValues: InitialValuesType = {
|
||||||
|
decimalPlaces: '6'
|
||||||
|
};
|
||||||
|
|
||||||
|
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||||
|
{
|
||||||
|
title: 'Convert time to decimal',
|
||||||
|
description:
|
||||||
|
'This example shows how to convert a formatted time (HH:MM:SS) to a decimal version.',
|
||||||
|
sampleText: '31:23:59',
|
||||||
|
sampleResult: `31.399722`,
|
||||||
|
sampleOptions: {
|
||||||
|
decimalPlaces: '6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
export default function ConvertTimeToDecimal({
|
||||||
|
title,
|
||||||
|
longDescription
|
||||||
|
}: ToolComponentProps) {
|
||||||
|
const [input, setInput] = useState<string>('');
|
||||||
|
const [result, setResult] = useState<string>('');
|
||||||
|
|
||||||
|
const compute = (values: InitialValuesType, input: string) => {
|
||||||
|
setResult(convertTimeToDecimal(input, values));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroups: GetGroupsType<InitialValuesType> | null = ({
|
||||||
|
values,
|
||||||
|
updateField
|
||||||
|
}) => [
|
||||||
|
{
|
||||||
|
title: 'Decimal places',
|
||||||
|
component: (
|
||||||
|
<Box>
|
||||||
|
<TextFieldWithDesc
|
||||||
|
description={'How many decimal places should the result contain?'}
|
||||||
|
value={values.decimalPlaces}
|
||||||
|
onOwnChange={(val) => updateField('decimalPlaces', val)}
|
||||||
|
type={'text'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<ToolContent
|
||||||
|
title={title}
|
||||||
|
input={input}
|
||||||
|
inputComponent={<ToolTextInput value={input} onChange={setInput} />}
|
||||||
|
resultComponent={<ToolTextResult value={result} />}
|
||||||
|
initialValues={initialValues}
|
||||||
|
exampleCards={exampleCards}
|
||||||
|
getGroups={getGroups}
|
||||||
|
setInput={setInput}
|
||||||
|
compute={compute}
|
||||||
|
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
15
src/pages/tools/time/convert-time-to-decimal/meta.ts
Normal file
15
src/pages/tools/time/convert-time-to-decimal/meta.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { defineTool } from '@tools/defineTool';
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
export const tool = defineTool('time', {
|
||||||
|
i18n: {
|
||||||
|
name: 'time:convertTimeToDecimal.title',
|
||||||
|
description: 'time:convertTimeToDecimal.description',
|
||||||
|
shortDescription: 'time:convertTimeToDecimal.shortDescription',
|
||||||
|
longDescription: 'time:convertTimeToDecimal.longDescription'
|
||||||
|
},
|
||||||
|
path: 'convert-time-to-decimal',
|
||||||
|
icon: 'material-symbols-light:decimal-increase-rounded',
|
||||||
|
keywords: ['convert', 'time', 'to', 'decimal'],
|
||||||
|
component: lazy(() => import('./index'))
|
||||||
|
});
|
||||||
37
src/pages/tools/time/convert-time-to-decimal/service.ts
Normal file
37
src/pages/tools/time/convert-time-to-decimal/service.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { InitialValuesType } from './types';
|
||||||
|
import { humanTimeValidation } from 'utils/time';
|
||||||
|
|
||||||
|
export function convertTimeToDecimal(
|
||||||
|
input: string,
|
||||||
|
options: InitialValuesType
|
||||||
|
): string {
|
||||||
|
if (!input) return '';
|
||||||
|
|
||||||
|
const dp = parseInt(options.decimalPlaces, 10);
|
||||||
|
if (isNaN(dp) || dp < 0) {
|
||||||
|
return 'Invalid decimal places value.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple lines processing
|
||||||
|
const lines = input.split('\n');
|
||||||
|
if (!lines) return '';
|
||||||
|
|
||||||
|
const result: string[] = [];
|
||||||
|
|
||||||
|
lines.forEach((line) => {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line) return;
|
||||||
|
|
||||||
|
const { isValid, hours, minutes, seconds } = humanTimeValidation(line);
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
result.push('Incorrect input format use `HH:MM:(SS)` or `HH.MM.(SS )`.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decimalTime = hours + minutes / 60 + seconds / 3600;
|
||||||
|
result.push(decimalTime.toFixed(dp).toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.join('\n');
|
||||||
|
}
|
||||||
3
src/pages/tools/time/convert-time-to-decimal/types.ts
Normal file
3
src/pages/tools/time/convert-time-to-decimal/types.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type InitialValuesType = {
|
||||||
|
decimalPlaces: string;
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { tool as timeConvertTimeToDecimal } from './convert-time-to-decimal/meta';
|
||||||
import { tool as timeConvertUnixToDate } from './convert-unix-to-date/meta';
|
import { tool as timeConvertUnixToDate } from './convert-unix-to-date/meta';
|
||||||
import { tool as timeCrontabGuru } from './crontab-guru/meta';
|
import { tool as timeCrontabGuru } from './crontab-guru/meta';
|
||||||
import { tool as timeBetweenDates } from './time-between-dates/meta';
|
import { tool as timeBetweenDates } from './time-between-dates/meta';
|
||||||
@ -17,5 +18,6 @@ export const timeTools = [
|
|||||||
timeBetweenDates,
|
timeBetweenDates,
|
||||||
timeCrontabGuru,
|
timeCrontabGuru,
|
||||||
checkLeapYear,
|
checkLeapYear,
|
||||||
timeConvertUnixToDate
|
timeConvertUnixToDate,
|
||||||
|
timeConvertTimeToDecimal
|
||||||
];
|
];
|
||||||
|
|||||||
58
src/utils/time.ts
Normal file
58
src/utils/time.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
type TimeValidationResult = {
|
||||||
|
isValid: boolean;
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates human-readable time format (HH:MM or HH:MM:SS)
|
||||||
|
* Supports either ':' or '.' as a separator, but not both
|
||||||
|
* @param {string} input - string time format
|
||||||
|
* * @returns {{
|
||||||
|
* isValid: boolean, // true if the input is a valid time
|
||||||
|
* hours: number, // parsed hours (0 or greater)
|
||||||
|
* minutes: number, // parsed minutes (0-59)
|
||||||
|
* seconds: number // parsed seconds (0-59, 0 if not provided)
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function humanTimeValidation(input: string): TimeValidationResult {
|
||||||
|
const result = { isValid: false, hours: 0, minutes: 0, seconds: 0 };
|
||||||
|
|
||||||
|
if (!input) return result;
|
||||||
|
|
||||||
|
input = input.trim();
|
||||||
|
|
||||||
|
// Operator use validation
|
||||||
|
// use of one between these two operators '.' or ':'
|
||||||
|
|
||||||
|
const hasColon = input.includes(':');
|
||||||
|
const hasDot = input.includes('.');
|
||||||
|
|
||||||
|
if (hasColon && hasDot) return result;
|
||||||
|
|
||||||
|
if (!hasColon && !hasDot) return result;
|
||||||
|
|
||||||
|
const separator = hasColon ? ':' : '.';
|
||||||
|
|
||||||
|
// Time parts validation
|
||||||
|
|
||||||
|
const parts = input.split(separator);
|
||||||
|
|
||||||
|
if (parts.length < 2 || parts.length > 3) return result;
|
||||||
|
|
||||||
|
const [h, m, s = '0'] = parts;
|
||||||
|
|
||||||
|
// every character should be a digit
|
||||||
|
if (![h, m, s].every((x) => /^\d+$/.test(x))) return result;
|
||||||
|
|
||||||
|
const hours = parseInt(h);
|
||||||
|
const minutes = parseInt(m);
|
||||||
|
const seconds = parseInt(s);
|
||||||
|
|
||||||
|
if (minutes < 0 || minutes > 59) return result;
|
||||||
|
if (seconds < 0 || seconds > 59) return result;
|
||||||
|
if (hours < 0) return result;
|
||||||
|
|
||||||
|
return { isValid: true, hours, minutes, seconds };
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user