diff --git a/public/locales/en/string.json b/public/locales/en/string.json index 142f8b9..2994890 100644 --- a/public/locales/en/string.json +++ b/public/locales/en/string.json @@ -257,5 +257,31 @@ "resultTitle": "Uppercase text", "shortDescription": "Convert text to uppercase", "title": "Convert to Uppercase" + }, + "urlEncode": { + "toolInfo": { + "description": "Load your string and it will automatically get URL-escaped.", + "shortDescription": "Quickly URL-escape a string.", + "longDescription": "This tool URL-encodes a string. Special URL characters get converted to percent-sign encoding. This encoding is called percent-encoding because each character's numeric value gets converted to a percent sign followed by a two-digit hexadecimal value. The hex values are determined based on the character's codepoint value. For example, a space gets escaped to %20, a colon to %3a, a slash to %2f. Characters that are not special stay unchanged. In case you also need to convert non-special characters to percent-encoding, then we've also added an extra option that lets you do that. Select the encode-non-special-chars option to enable this behavior.", + "title": "String URL encoder" + }, + "encodingOption": { + "title": "Encoding Options", + "nonSpecialCharPlaceholder": "Encode non-special characters", + "nonSpecialCharDescription": "If selected, then all characters in the input string will be converted to URL-encoding (not just special)." + }, + "inputTitle": "Input String", + "resultTitle": "Url-escaped String" + }, + "urlDecode": { + "toolInfo": { + "description": "Load your string and it will automatically get URL-unescaped.", + "shortDescription": "Quickly URL-unescape a string.", + "longDescription": "This tool URL-decodes a previously URL-encoded string. URL-decoding is the inverse operation of URL-encoding. All percent-encoded characters get decoded to characters that you can understand. Some of the most well known percent-encoded values are %20 for a space, %3a for a colon, %2f for a slash, and %3f for a question mark. The two digits following the percent sign are character's char code values in hex.", + "title": "String URL decoder" + }, + + "inputTitle": "Input String(URL-escaped)", + "resultTitle": "Output string" } } diff --git a/public/locales/en/time.json b/public/locales/en/time.json index 3b3e93a..1eb65bd 100644 --- a/public/locales/en/time.json +++ b/public/locales/en/time.json @@ -98,5 +98,21 @@ "zeroPaddingDescription": "Make all time components always be two digits wide.", "zeroPrintDescription": "Display the dropped parts as zero values \"00\".", "zeroPrintTruncatedParts": "Zero-print Truncated Parts" + }, + "convertUnixToDate": { + "title": "Convert Unix to Date", + "description": "Convert a Unix timestamp to a human-readable date.", + "shortDescription": "Convert Unix timestamp to date", + "longDescription": "", + "withLabel": "Options", + "outputOptions": "Output Options", + "addUtcLabel": "Add 'UTC' suffix", + "addUtcLabelDescription": "Display 'UTC' after the converted date (only for UTC mode)", + "useLocalTime": "Use Local Time", + "useLocalTimeDescription": "Show converted date in your local timezone instead of UTC", + "toolInfo": { + "title": "Convert Unix to Date", + "description": "This tool converts a Unix timestamp (in seconds) into a human-readable date format (e.g., YYYY-MM-DD HH:MM:SS). It supports both local and UTC output, making it useful for quickly interpreting timestamps from logs, APIs, or systems that use Unix time." + } } } diff --git a/public/locales/fr/time.json b/public/locales/fr/time.json index e242426..c8f6fbb 100644 --- a/public/locales/fr/time.json +++ b/public/locales/fr/time.json @@ -96,5 +96,21 @@ "zeroPaddingDescription": "Faites en sorte que tous les composants de temps aient toujours une largeur de deux chiffres.", "zeroPrintDescription": "Afficher les parties supprimées sous forme de valeurs nulles « 00 ».", "zeroPrintTruncatedParts": "Parties tronquées sans impression" + }, + "convertUnixToDate": { + "title": "Convertir un timestamp Unix en date", + "description": "Convertit un timestamp Unix en une date lisible par un humain.", + "shortDescription": "Conversion de timestamp Unix en date", + "longDescription": "Cet outil permet de convertir un timestamp Unix (en secondes) en une date lisible au format AAAA-MM-JJ HH:MM:SS. Il prend en charge l'affichage en UTC ou dans le fuseau horaire local, ce qui est pratique pour interpréter rapidement des horodatages issus de journaux, d'API ou de systèmes utilisant le temps Unix.", + "withLabel": "Options", + "outputOptions": "Options de sortie", + "addUtcLabel": "Ajouter le suffixe 'UTC'", + "addUtcLabelDescription": "Affiche 'UTC' après la date convertie (uniquement en mode UTC)", + "useLocalTime": "Utiliser l’heure locale", + "useLocalTimeDescription": "Affiche la date convertie dans votre fuseau horaire local au lieu de l’heure UTC", + "toolInfo": { + "title": "Convertir un timestamp Unix en date", + "description": "Cet outil convertit un timestamp Unix (en secondes) en une date lisible (par ex. AAAA-MM-JJ HH:MM:SS). Il prend en charge l'affichage en heure locale ou en UTC, ce qui le rend utile pour analyser rapidement des données issues de journaux ou d’APIs." + } } } diff --git a/src/pages/tools/string/index.ts b/src/pages/tools/string/index.ts index 0ded96d..bd645f7 100644 --- a/src/pages/tools/string/index.ts +++ b/src/pages/tools/string/index.ts @@ -18,6 +18,8 @@ import { tool as stringBase64 } from './base64/meta'; import { tool as stringStatistic } from './statistic/meta'; import { tool as stringCensor } from './censor/meta'; import { tool as stringPasswordGenerator } from './password-generator/meta'; +import { tool as stringEncodeUrl } from './url-encode/meta'; +import { tool as StringDecodeUrl } from './url-decode/meta'; export const stringTools = [ stringSplit, @@ -39,5 +41,7 @@ export const stringTools = [ stringBase64, stringStatistic, stringCensor, - stringPasswordGenerator + stringPasswordGenerator, + stringEncodeUrl, + StringDecodeUrl ]; diff --git a/src/pages/tools/string/url-decode/index.tsx b/src/pages/tools/string/url-decode/index.tsx new file mode 100644 index 0000000..49d4a29 --- /dev/null +++ b/src/pages/tools/string/url-decode/index.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { decodeString } from './service'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolContent from '@components/ToolContent'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import { ToolComponentProps } from '@tools/defineTool'; +import { useTranslation } from 'react-i18next'; + +const initialValues = {}; + +const exampleCards: CardExampleType[] = [ + { + title: 'Decode an actual URL', + description: + 'This example decodes a URL-encoded string back to its readable URL form.', + sampleText: 'https%3A%2F%2Fomnitools.app%2F', + sampleResult: 'https://omnitools.app/', + sampleOptions: initialValues + }, + { + title: 'Decode All Characters', + description: + 'This example decodes a string where every character has been URL-encoded, restoring the original readable text.', + sampleText: + '%49%20%63%61%6E%27%74%20%62%65%6C%69%65%76%65%20%69%74%27%73%20%6E%6F%74%20%62%75%74%74%65%72%21', + sampleResult: "I can't believe it's not butter!", + sampleOptions: initialValues + } +]; + +export default function DecodeString({ + title, + longDescription +}: ToolComponentProps) { + const { t } = useTranslation('string'); + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + function compute(_initialValues: typeof initialValues, input: string) { + setResult(decodeString(input)); + } + + return ( + + } + resultComponent={ + + } + toolInfo={{ + title: t('urlDecode.toolInfo.title', { title }), + description: longDescription + }} + exampleCards={exampleCards} + /> + ); +} diff --git a/src/pages/tools/string/url-decode/meta.ts b/src/pages/tools/string/url-decode/meta.ts new file mode 100644 index 0000000..986a396 --- /dev/null +++ b/src/pages/tools/string/url-decode/meta.ts @@ -0,0 +1,16 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('string', { + path: 'url-decode-string', + icon: 'codicon:symbol-string', + + keywords: ['uppercase'], + component: lazy(() => import('./index')), + i18n: { + name: 'string:urlDecode.toolInfo.title', + description: 'string:urlDecode.toolInfo.description', + shortDescription: 'string:urlDecode.toolInfo.shortDescription', + longDescription: 'string:urlDecode.toolInfo.longDescription' + } +}); diff --git a/src/pages/tools/string/url-decode/service.ts b/src/pages/tools/string/url-decode/service.ts new file mode 100644 index 0000000..7eaef1c --- /dev/null +++ b/src/pages/tools/string/url-decode/service.ts @@ -0,0 +1,4 @@ +export function decodeString(input: string): string { + if (!input) return ''; + return decodeURIComponent(input); +} diff --git a/src/pages/tools/string/url-encode/index.tsx b/src/pages/tools/string/url-encode/index.tsx new file mode 100644 index 0000000..27d81f5 --- /dev/null +++ b/src/pages/tools/string/url-encode/index.tsx @@ -0,0 +1,98 @@ +import { Box } from '@mui/material'; +import { useState } from 'react'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { GetGroupsType } from '@components/options/ToolOptions'; +import { encodeString } from './service'; +import ToolTextInput from '@components/input/ToolTextInput'; +import { InitialValuesType } from './types'; +import ToolContent from '@components/ToolContent'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import { ToolComponentProps } from '@tools/defineTool'; +import CheckboxWithDesc from '@components/options/CheckboxWithDesc'; +import { useTranslation } from 'react-i18next'; + +const initialValues: InitialValuesType = { + nonSpecialChar: false +}; + +const exampleCards: CardExampleType[] = [ + { + title: 'Encode an actual URL', + description: + 'This example URL-encodes a string that also happens to be a valid web link. Special characters in this example are a colon, slash, question mark and equals sign.', + sampleText: 'https://omnitools.app/', + sampleResult: 'https%3A%2F%2Fomnitools.app%2F', + sampleOptions: initialValues + }, + { + title: 'Encode All Characters', + description: + "In this example, we've enabled the option that encodes absolutely all characters in a string to URL-encoding. This option makes non-special characters, such as letters get encoded to their hex codes prefixed by a percent sign.", + sampleText: "I can't believe it's not butter!", + sampleResult: + '%49%20%63%61%6E%27%74%20%62%65%6C%69%65%76%65%20%69%74%27%73%20%6E%6F%74%20%62%75%74%74%65%72%21', + sampleOptions: { + nonSpecialChar: true + } + } +]; + +export default function EncodeString({ + title, + longDescription +}: ToolComponentProps) { + const { t } = useTranslation('string'); + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + function compute(initialValues: InitialValuesType, input: string) { + setResult(encodeString(input, initialValues)); + } + + const getGroups: GetGroupsType = ({ + values, + updateField + }) => [ + { + title: t('urlEncode.encodingOption.title'), + component: ( + + updateField('nonSpecialChar', value)} + title={t('urlEncode.encodingOption.nonSpecialCharPlaceholder')} + description={t( + 'urlEncode.encodingOption.nonSpecialCharDescription' + )} + /> + + ) + } + ]; + + return ( + + } + resultComponent={ + + } + toolInfo={{ + title: t('urlEncode.toolInfo.title', { title }), + description: longDescription + }} + exampleCards={exampleCards} + /> + ); +} diff --git a/src/pages/tools/string/url-encode/meta.ts b/src/pages/tools/string/url-encode/meta.ts new file mode 100644 index 0000000..3774fca --- /dev/null +++ b/src/pages/tools/string/url-encode/meta.ts @@ -0,0 +1,16 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('string', { + path: 'url-encode-string', + icon: 'ic:baseline-percentage', + + keywords: ['uppercase'], + component: lazy(() => import('./index')), + i18n: { + name: 'string:urlEncode.toolInfo.title', + description: 'string:urlEncode.toolInfo.description', + shortDescription: 'string:urlEncode.toolInfo.shortDescription', + longDescription: 'string:urlEncode.toolInfo.longDescription' + } +}); diff --git a/src/pages/tools/string/url-encode/service.ts b/src/pages/tools/string/url-encode/service.ts new file mode 100644 index 0000000..207fb30 --- /dev/null +++ b/src/pages/tools/string/url-encode/service.ts @@ -0,0 +1,18 @@ +import { InitialValuesType } from './types'; + +export function encodeString( + input: string, + options: InitialValuesType +): string { + if (!input) return ''; + if (!options.nonSpecialChar) { + return encodeURIComponent(input); + } + + let result = ''; + for (const char of input) { + const hex = char.codePointAt(0)!.toString(16).toUpperCase(); + result += '%' + hex.padStart(2, '0'); + } + return result; +} diff --git a/src/pages/tools/string/url-encode/types.ts b/src/pages/tools/string/url-encode/types.ts new file mode 100644 index 0000000..2d4d3f0 --- /dev/null +++ b/src/pages/tools/string/url-encode/types.ts @@ -0,0 +1,3 @@ +export type InitialValuesType = { + nonSpecialChar: boolean; +}; diff --git a/src/pages/tools/time/convert-unix-to-date/convert-unix-to-date.service.test.ts b/src/pages/tools/time/convert-unix-to-date/convert-unix-to-date.service.test.ts new file mode 100644 index 0000000..23684e6 --- /dev/null +++ b/src/pages/tools/time/convert-unix-to-date/convert-unix-to-date.service.test.ts @@ -0,0 +1,56 @@ +import { expect, describe, it } from 'vitest'; +import { convertUnixToDate } from './service'; + +describe('convertUnixToDate', () => { + it('should convert a single Unix timestamp with label (UTC)', () => { + const input = '0'; + const result = convertUnixToDate(input, true, false); + expect(result).toBe('1970-01-01 00:00:00.000 UTC'); + }); + + it('should convert a single Unix timestamp without label (UTC)', () => { + const input = '1234567890'; + const result = convertUnixToDate(input, false, false); + expect(result).toBe('2009-02-13 23:31:30.000'); + }); + + it('should convert a single Unix timestamp in local time', () => { + const input = '1234567890'; + const result = convertUnixToDate(input, true, true); + expect(result.endsWith('UTC')).toBe(false); + }); + + it('should handle multiple lines with label (UTC)', () => { + const input = '0\n2147483647'; + const result = convertUnixToDate(input, true, false); + expect(result).toBe( + '1970-01-01 00:00:00.000 UTC\n2038-01-19 03:14:07.000 UTC' + ); + }); + + it('should handle multiple lines with local time', () => { + const input = '1672531199\n1721287227'; + const result = convertUnixToDate(input, false, true); + const lines = result.split('\n'); + expect(lines.length).toBe(2); + expect(lines[0].endsWith('UTC')).toBe(false); + }); + + it('should return empty string for invalid input', () => { + const input = 'not_a_number'; + const result = convertUnixToDate(input, true, false); + expect(result).toBe(''); + }); + + it('should return empty string for empty input', () => { + const input = ''; + const result = convertUnixToDate(input, false, false); + expect(result).toBe(''); + }); + + it('should ignore invalid lines in multiline input', () => { + const input = 'abc\n1600000000'; + const result = convertUnixToDate(input, true, false); + expect(result).toBe('\n2020-09-13 12:26:40.000 UTC'); + }); +}); diff --git a/src/pages/tools/time/convert-unix-to-date/index.tsx b/src/pages/tools/time/convert-unix-to-date/index.tsx new file mode 100644 index 0000000..77349d3 --- /dev/null +++ b/src/pages/tools/time/convert-unix-to-date/index.tsx @@ -0,0 +1,102 @@ +import { Box } from '@mui/material'; +import { 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 { convertUnixToDate } from './service'; +import { useTranslation } from 'react-i18next'; + +const initialValues = { + withLabel: true, + useLocalTime: false +}; +type InitialValuesType = typeof initialValues; + +const exampleCards: CardExampleType[] = [ + { + title: 'Basic Unix Time to Date', + description: + 'This example shows how Unix timestamps are converted into human-readable dates. Each timestamp represents the number of seconds that have elapsed since January 1, 1970 (UTC).', + sampleText: `0 +1721287227 +2147483647`, + sampleResult: `1970-01-01 00:00:00:000 UTC +2024-07-18 10:00:27:000 UTC +2038-01-19 03:14:07:000 UTC`, + sampleOptions: { withLabel: true, useLocalTime: false } + }, + { + title: 'Without UTC Suffix', + description: + 'In this example, the UTC suffix is removed from the output. This might be useful for embedding timestamps into other formats or for cleaner display.', + sampleText: `1234567890 +1672531199`, + sampleResult: `2009-02-13 23:31:30 +2022-12-31 23:59:59:000`, + sampleOptions: { withLabel: false, useLocalTime: false } + }, + { + title: 'Use Local Time', + description: + 'This example demonstrates how timestamps are shown in your local timezone rather than UTC. The UTC suffix is omitted in this case.', + sampleText: `1721287227`, + sampleResult: `2024-07-18 12:00:27`, + sampleOptions: { withLabel: true, useLocalTime: true } + } +]; + +export default function ConvertUnixToDate({ title }: ToolComponentProps) { + const { t } = useTranslation('time'); + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (values: typeof initialValues, input: string) => { + setResult(convertUnixToDate(input, values.withLabel, values.useLocalTime)); + }; + + const getGroups: GetGroupsType | null = ({ + values, + updateField + }) => [ + { + title: t('convertUnixToDate.withLabel'), + component: ( + + updateField('withLabel', val)} + checked={values.withLabel} + title={t('convertUnixToDate.addUtcLabel')} + description={t('convertUnixToDate.addUtcLabelDescription')} + /> + updateField('useLocalTime', val)} + checked={values.useLocalTime} + title={t('convertUnixToDate.useLocalTime')} + description={t('convertUnixToDate.useLocalTimeDescription')} + /> + + ) + } + ]; + return ( + } + resultComponent={} + initialValues={initialValues} + exampleCards={exampleCards} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ + title: t('convertUnixToDate.toolInfo.title'), + description: t('convertUnixToDate.toolInfo.description') + }} + /> + ); +} diff --git a/src/pages/tools/time/convert-unix-to-date/meta.ts b/src/pages/tools/time/convert-unix-to-date/meta.ts new file mode 100644 index 0000000..a3128f0 --- /dev/null +++ b/src/pages/tools/time/convert-unix-to-date/meta.ts @@ -0,0 +1,15 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('time', { + i18n: { + name: 'time:convertUnixToDate.title', + description: 'time:convertUnixToDate.description', + shortDescription: 'time:convertUnixToDate.shortDescription', + longDescription: 'time:convertUnixToDate.longDescription' + }, + path: 'convert-unix-to-date', + icon: 'material-symbols:schedule', + keywords: ['convert', 'unix', 'to', 'date'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/time/convert-unix-to-date/service.ts b/src/pages/tools/time/convert-unix-to-date/service.ts new file mode 100644 index 0000000..8f1ec0d --- /dev/null +++ b/src/pages/tools/time/convert-unix-to-date/service.ts @@ -0,0 +1,42 @@ +import { containsOnlyDigits } from '@utils/string'; + +function computeUnixToDate(input: string, useLocalTime: boolean): string { + if (!containsOnlyDigits(input)) { + return ''; + } + const timestamp = parseInt(input, 10); + const date = new Date(timestamp * 1000); // Convert from seconds to milliseconds + + if (useLocalTime) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + } else { + return date.toISOString().replace('T', ' ').replace('Z', ''); + } +} + +export function convertUnixToDate( + input: string, + withLabel: boolean, + useLocalTime: boolean +): string { + const result: string[] = []; + + const lines = input.split('\n'); + + lines.forEach((line) => { + const parts = line.split(' '); + const timestamp = parts[0]; + const formattedDate = computeUnixToDate(timestamp, useLocalTime); + + const label = !useLocalTime && withLabel ? ' UTC' : ''; + result.push(formattedDate ? `${formattedDate}${label}` : ''); + }); + + return result.join('\n'); +} diff --git a/src/pages/tools/time/index.ts b/src/pages/tools/time/index.ts index 89dd5cc..a98d27e 100644 --- a/src/pages/tools/time/index.ts +++ b/src/pages/tools/time/index.ts @@ -1,3 +1,4 @@ +import { tool as timeConvertUnixToDate } from './convert-unix-to-date/meta'; import { tool as timeCrontabGuru } from './crontab-guru/meta'; import { tool as timeBetweenDates } from './time-between-dates/meta'; import { tool as daysDoHours } from './convert-days-to-hours/meta'; @@ -15,5 +16,6 @@ export const timeTools = [ truncateClockTime, timeBetweenDates, timeCrontabGuru, - checkLeapYear + checkLeapYear, + timeConvertUnixToDate ];