diff --git a/src/pages/tools/time/index.ts b/src/pages/tools/time/index.ts index e42bb7f..f229937 100644 --- a/src/pages/tools/time/index.ts +++ b/src/pages/tools/time/index.ts @@ -2,10 +2,12 @@ import { tool as daysDoHours } from './convert-days-to-hours/meta'; import { tool as hoursToDays } from './convert-hours-to-days/meta'; import { tool as convertSecondsToTime } from './convert-seconds-to-time/meta'; import { tool as convertTimetoSeconds } from './convert-time-to-seconds/meta'; +import { tool as truncateClockTime } from './truncate-clock-time/meta'; export const timeTools = [ daysDoHours, hoursToDays, convertSecondsToTime, - convertTimetoSeconds + convertTimetoSeconds, + truncateClockTime ]; diff --git a/src/pages/tools/time/truncate-clock-time/index.tsx b/src/pages/tools/time/truncate-clock-time/index.tsx new file mode 100644 index 0000000..ae76280 --- /dev/null +++ b/src/pages/tools/time/truncate-clock-time/index.tsx @@ -0,0 +1,151 @@ +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 CheckboxWithDesc from '@components/options/CheckboxWithDesc'; +import SimpleRadio from '@components/options/SimpleRadio'; +import { truncateClockTime } from './service'; + +const initialValues = { + onlySecond: true, + zeroPrint: false, + zeroPadding: true +}; +type InitialValuesType = typeof initialValues; +const exampleCards: CardExampleType[] = [ + { + title: 'Truncate Seconds', + description: + 'In this example, we get rid of the seconds part from several timer values. We select the "Truncate Only Seconds" mode and get a list of timer values consisting only of hours and minutes in format "hh:mm" (the ":ss" part is removed).', + sampleText: `01:28:06 +07:39:56 +02:12:41 +10:10:38`, + sampleResult: `01:28 +07:39 +02:12 +10:10`, + sampleOptions: { onlySecond: true, zeroPrint: false, zeroPadding: true } + }, + { + title: 'Truncate Minutes and Seconds', + description: + 'This example truncates five clock times to an hour. It drops the minutes and seconds parts and only outputs the hours with zero padding.', + sampleText: `04:42:03 +07:09:59 +11:29:16 +21:30:45 +13:03:09`, + sampleResult: `04 +07 +11 +21 +13`, + sampleOptions: { onlySecond: false, zeroPrint: false, zeroPadding: true } + }, + { + title: 'Set Seconds to Zero', + description: + 'In this example, we set the seconds part of each time to zero by first truncating the time to minutes and then appending a zero at the end in place of the truncated seconds. To do this, we switch on the seconds-truncation mode and activate the option to print-zero-time-parts. We also turn off the padding option and get the output time in format "h:m:s", where the seconds are always zero so the final format is "h:m:0".', + sampleText: `17:25:55 +10:16:07 +12:02:09 +06:05:11`, + sampleResult: `17:25:0 +10:16:0 +12:2:0 +6:5:0`, + sampleOptions: { onlySecond: true, zeroPrint: true, zeroPadding: true } + } +]; + +export default function TruncateClockTime({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (optionsValues: typeof initialValues, input: string) => { + setResult( + truncateClockTime( + input, + optionsValues.onlySecond, + optionsValues.zeroPrint, + optionsValues.zeroPadding + ) + ); + }; + + const getGroups: GetGroupsType | null = ({ + values, + updateField + }) => [ + { + title: 'Truncation Side', + component: ( + + updateField('onlySecond', true)} + checked={values.onlySecond} + title={'Truncate Only Seconds'} + description={'Drop the seconds component from each clock time.'} + /> + updateField('onlySecond', false)} + checked={!values.onlySecond} + title={'Truncate Minutes and Seconds'} + description={ + 'Drop both – the minutes and seconds components from each clock time.' + } + /> + + ) + }, + { + title: 'Print Dropped Components', + component: ( + + updateField('zeroPrint', val)} + checked={values.zeroPrint} + title={'Zero-print Truncated Parts'} + description={'Display the dropped parts as zero values "00".'} + /> + + ) + }, + { + title: 'Time Padding', + component: ( + + updateField('zeroPadding', val)} + checked={values.zeroPadding} + title={'Use Zero Padding'} + description={'Make all time components always be two digits wide.'} + /> + + ) + } + ]; + + return ( + } + resultComponent={} + initialValues={initialValues} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + exampleCards={exampleCards} + /> + ); +} diff --git a/src/pages/tools/time/truncate-clock-time/meta.ts b/src/pages/tools/time/truncate-clock-time/meta.ts new file mode 100644 index 0000000..9404c1a --- /dev/null +++ b/src/pages/tools/time/truncate-clock-time/meta.ts @@ -0,0 +1,15 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('time', { + path: 'truncate-clock-time', + name: 'Truncate Clock Time', + icon: 'mdi:clock-remove-outline', + description: + 'With this browser-based application, you can truncate a clock time and drop the minutes and/or seconds components from it. If you drop the seconds, you will be left with hours and minutes. For example, "13:23:45" will be truncated to "13:23". If you drop both minutes and seconds, you will be left with just hours. For example, "13:23:45" will be truncated to just "13". Additionally, in the options, you can add or remove the hours and minutes padding and also print the dropped time component as a zero if needed.', + shortDescription: 'Quickly convert clock time in H:M:S format to seconds.', + keywords: ['truncate', 'time', 'clock'], + longDescription: + 'This is a quick online utility for truncating the given clock times or timer values. It allows you to get rid of the least significant time components such as seconds and minutes. For example, if you have an exact time of "09:45:37" and you are ok working with a less precise time that has just hours and minutes, then you can discard the seconds and get "09:45". Similarly, if you only need the hours in the output, then you can truncate the minutes as well and get "09". In general, if you have a clock time "hh:mm:ss", then removing the seconds will leave "hh:mm" and removing both minutes and seconds will leave just "hh". The program can truncate as many clock times as you need – just enter them one per line in the input and the truncation result will be printed in the output where you can easily copy it from. By default, the truncated parts of the time are not displayed, but if necessary, you can print them as zeros. For example, if you drop minutes and seconds from "09:45:37" and you enable zero component printing, then the output will be "09:00:00". Finally, you can control the digit width of the clock display. For example, the time "09:00:00" has full zero-padding (the width is two digits for each time component) but if the padding is removed, then it will be shown as "9:0:0" (the width now is one digit). Timeabulous!', + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/time/truncate-clock-time/service.ts b/src/pages/tools/time/truncate-clock-time/service.ts new file mode 100644 index 0000000..96eddca --- /dev/null +++ b/src/pages/tools/time/truncate-clock-time/service.ts @@ -0,0 +1,64 @@ +import { containsOnlyDigits } from '@utils/string'; + +function compute( + timeArray: string[], + lineNumber: number, + onlySeconds: boolean, + zeroPrint: boolean, + zeroPadding: boolean +): string { + if (timeArray[0] == '') { + return ''; + } + if (timeArray.length > 3) { + throw new Error(`Time contains more than 3 parts on line ${lineNumber}`); + } + [...timeArray, '0', '0'].forEach((time, index) => { + if (!containsOnlyDigits(time)) { + throw new Error( + `Time doesn't contain valid ${ + ['hours', 'minutes', 'seconds'][index] + } on line ${lineNumber}` + ); + } + }); + + const slicedArray = onlySeconds + ? timeArray.slice(0, 2) + : timeArray.slice(0, 1); + + if (zeroPrint) { + onlySeconds ? slicedArray.push('0') : slicedArray.push('0', '0'); + } + + return zeroPadding + ? slicedArray + .map((unit) => String(unit).padStart(2, '0')) // Ensures two-digit format + .join(':') + : slicedArray.join(':'); +} + +export function truncateClockTime( + input: string, + onlySeconds: boolean, + zeroPrint: boolean, + zeroPadding: boolean +): string { + const result: string[] = []; + + const lines = input.split('\n'); + + lines.forEach((line, index) => { + const timeArray = line.split(':'); + const truncatedTime = compute( + timeArray, + index + 1, + onlySeconds, + zeroPrint, + zeroPadding + ); + result.push(truncatedTime); + }); + + return result.join('\n'); +} diff --git a/src/pages/tools/time/truncate-clock-time/truncate-clock-time.service.test.ts b/src/pages/tools/time/truncate-clock-time/truncate-clock-time.service.test.ts new file mode 100644 index 0000000..f327c97 --- /dev/null +++ b/src/pages/tools/time/truncate-clock-time/truncate-clock-time.service.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect } from 'vitest'; +import { truncateClockTime } from './service'; + +describe('truncateClockTime', () => { + it('should truncate time to hours only without zero padding', () => { + const input = '12:34:56\n07:08:09'; + const result = truncateClockTime(input, false, false, false); + expect(result).toBe('12\n07'); + }); + it('should truncate time to hours only without zero padding 2', () => { + const input = '12:34:56\n7:08:09'; + const result = truncateClockTime(input, false, false, false); + expect(result).toBe('12\n7'); + }); + + it('should truncate time to hours and minutes with zero padding', () => { + const input = '3:4:5\n7:8:9'; + const result = truncateClockTime(input, true, false, true); + expect(result).toBe('03:04\n07:08'); + }); + + it('should handle empty input gracefully', () => { + const input = ''; + const result = truncateClockTime(input, false, false, false); + expect(result).toBe(''); + }); + + it('should throw an error if time contains more than 3 parts', () => { + const input = '12:34:56:78'; + expect(() => truncateClockTime(input, false, false, false)).toThrow( + 'Time contains more than 3 parts on line 1' + ); + }); + + it('should throw an error if time contains invalid characters', () => { + const input = '12:34:ab'; + expect(() => truncateClockTime(input, false, false, false)).toThrow( + "Time doesn't contain valid seconds on line 1" + ); + }); + + it('should add zero seconds and minutes when zeroPrint is true', () => { + const input = '12'; + const result = truncateClockTime(input, false, true, false); + expect(result).toBe('12:0:0'); + }); + + it('should pad single-digit hours, minutes, and seconds when zeroPadding is true', () => { + const input = '1:2:3'; + const result = truncateClockTime(input, true, true, true); + expect(result).toBe('01:02:00'); + }); + + it('should handle multiple lines of input correctly', () => { + const input = '12:34:56\n1:2:3\n7:8'; + const result = truncateClockTime(input, true, true, true); + expect(result).toBe('12:34:00\n01:02:00\n07:08:00'); + }); +});