diff --git a/src/components/input/ToolTextInput.tsx b/src/components/input/ToolTextInput.tsx index e5741f6..1d6f3e1 100644 --- a/src/components/input/ToolTextInput.tsx +++ b/src/components/input/ToolTextInput.tsx @@ -7,11 +7,13 @@ import InputFooter from './InputFooter'; export default function ToolTextInput({ value, onChange, - title = 'Input text' + title = 'Input text', + placeholder }: { title?: string; value: string; onChange: (value: string) => void; + placeholder?: string; }) { const { showSnackBar } = useContext(CustomSnackBarContext); const fileInputRef = useRef(null); @@ -48,6 +50,7 @@ export default function ToolTextInput({ value={value} onChange={(event) => onChange(event.target.value)} fullWidth + placeholder={placeholder} multiline rows={10} sx={{ diff --git a/src/pages/tools/time/epoch-converter/epoch-converter.service.test.ts b/src/pages/tools/time/epoch-converter/epoch-converter.service.test.ts new file mode 100644 index 0000000..efb0754 --- /dev/null +++ b/src/pages/tools/time/epoch-converter/epoch-converter.service.test.ts @@ -0,0 +1,30 @@ +import { expect, describe, it } from 'vitest'; +import { epochToDate, dateToEpoch, main } from './service'; + +describe('epoch-converter service', () => { + it('converts epoch (seconds) to date', () => { + expect(epochToDate('1609459200')).toBe('Fri, 01 Jan 2021 00:00:00 GMT'); + }); + it('converts epoch (milliseconds) to date', () => { + expect(epochToDate('1609459200000')).toBe('Fri, 01 Jan 2021 00:00:00 GMT'); + }); + it('returns error for invalid epoch', () => { + expect(epochToDate('notanumber')).toMatch(/Invalid epoch/); + }); + it('converts date string to epoch', () => { + expect(dateToEpoch('2021-01-01T00:00:00Z')).toBe('1609459200'); + }); + it('returns error for invalid date string', () => { + expect(dateToEpoch('notadate')).toMatch(/Invalid date/); + }); + it('main: detects and converts epoch', () => { + expect(main('1609459200', {})).toBe('Fri, 01 Jan 2021 00:00:00 GMT'); + }); + it('main: detects and converts date', () => { + expect(main('2021-01-01T00:00:00Z', {})).toBe('1609459200'); + }); + it('main: returns error for invalid input', () => { + expect(main('notadate', {})).toMatch(/Invalid date/); + expect(main('notanumber', {})).toMatch(/Invalid date/); + }); +}); diff --git a/src/pages/tools/time/epoch-converter/index.tsx b/src/pages/tools/time/epoch-converter/index.tsx new file mode 100644 index 0000000..ff151d8 --- /dev/null +++ b/src/pages/tools/time/epoch-converter/index.tsx @@ -0,0 +1,143 @@ +import { Box, Stack, Button, Alert } 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 { main, epochToDate, dateToEpoch } from './service'; +import { InitialValuesType } from './types'; + +const initialValues: InitialValuesType = {}; + +const exampleCards: CardExampleType[] = [ + { + title: 'Epoch to Date (seconds)', + description: 'Convert Unix timestamp (seconds) to date', + sampleText: '1609459200', + sampleResult: 'Fri, 01 Jan 2021 00:00:00 GMT', + sampleOptions: {} + }, + { + title: 'Epoch to Date (milliseconds)', + description: 'Convert Unix timestamp (milliseconds) to date', + sampleText: '1609459200000', + sampleResult: 'Fri, 01 Jan 2021 00:00:00 GMT', + sampleOptions: {} + }, + { + title: 'Date to Epoch', + description: 'Convert date string to Unix timestamp (seconds)', + sampleText: '2021-01-01T00:00:00Z', + sampleResult: '1609459200', + sampleOptions: {} + } +]; + +export default function EpochConverter({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + const [hasInteracted, setHasInteracted] = useState(false); + const [isValid, setIsValid] = useState(null); + + const compute = (_values: InitialValuesType, input: string) => { + let output = main(input, {}); + const invalid = output.startsWith('Invalid'); + setIsValid(!invalid); + setResult(output); + }; + + const handleExample = (expr: string) => { + setInput(expr); + setHasInteracted(true); + compute({}, expr); + }; + + const handleInputChange = (val: string) => { + if (!hasInteracted) setHasInteracted(true); + setInput(val); + }; + + return ( + + + + {exampleCards.map((ex, i) => ( + + ))} + + + } + resultComponent={ +
+ {hasInteracted && isValid === false && ( +
+ + Invalid input. Please enter a valid epoch timestamp or date + string. + +
+ )} +
+ +
+
+ } + initialValues={initialValues} + exampleCards={exampleCards} + getGroups={null} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + /> + ); +} diff --git a/src/pages/tools/time/epoch-converter/meta.ts b/src/pages/tools/time/epoch-converter/meta.ts new file mode 100644 index 0000000..c163dce --- /dev/null +++ b/src/pages/tools/time/epoch-converter/meta.ts @@ -0,0 +1,23 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('time', { + name: 'Epoch Converter', + path: 'epoch-converter', + icon: 'mdi:clock-time-four-outline', + description: + 'Convert Unix epoch timestamps to human-readable dates and vice versa.', + shortDescription: 'Convert between Unix timestamps and dates.', + keywords: [ + 'epoch', + 'converter', + 'timestamp', + 'date', + 'unix', + 'time', + 'convert' + ], + longDescription: + 'Enter a Unix timestamp (in seconds or milliseconds) to get a human-readable date, or enter a date string (e.g., 2021-01-01T00:00:00Z) to get the corresponding Unix timestamp. Useful for developers, sysadmins, and anyone working with time data.', + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/time/epoch-converter/service.ts b/src/pages/tools/time/epoch-converter/service.ts new file mode 100644 index 0000000..477b87c --- /dev/null +++ b/src/pages/tools/time/epoch-converter/service.ts @@ -0,0 +1,26 @@ +import { InitialValuesType } from './types'; + +export function epochToDate(input: string): string { + const num = Number(input); + if (isNaN(num)) return 'Invalid epoch timestamp.'; + // Support both seconds and milliseconds + const date = new Date(num > 1e12 ? num : num * 1000); + if (isNaN(date.getTime())) return 'Invalid epoch timestamp.'; + return date.toUTCString(); +} + +export function dateToEpoch(input: string): string { + const date = new Date(input); + if (isNaN(date.getTime())) return 'Invalid date string.'; + return Math.floor(date.getTime() / 1000).toString(); +} + +export function main(input: string, _options: any): string { + if (!input.trim()) return ''; + // If input is a number, treat as epoch + if (/^-?\d+(\.\d+)?$/.test(input.trim())) { + return epochToDate(input.trim()); + } + // Otherwise, treat as date string + return dateToEpoch(input.trim()); +} diff --git a/src/pages/tools/time/epoch-converter/types.ts b/src/pages/tools/time/epoch-converter/types.ts new file mode 100644 index 0000000..d4135c9 --- /dev/null +++ b/src/pages/tools/time/epoch-converter/types.ts @@ -0,0 +1,3 @@ +export type InitialValuesType = { + // splitSeparator: string; +}; diff --git a/src/pages/tools/time/index.ts b/src/pages/tools/time/index.ts index 9b80e65..79c2cd4 100644 --- a/src/pages/tools/time/index.ts +++ b/src/pages/tools/time/index.ts @@ -1,3 +1,4 @@ +import { tool as timeEpochConverter } from './epoch-converter/meta'; import { tool as timeBetweenDates } from './time-between-dates/meta'; import { tool as daysDoHours } from './convert-days-to-hours/meta'; import { tool as hoursToDays } from './convert-hours-to-days/meta'; @@ -11,5 +12,6 @@ export const timeTools = [ convertSecondsToTime, convertTimetoSeconds, truncateClockTime, - timeBetweenDates + timeBetweenDates, + timeEpochConverter ];